/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
/*****************************************************************************
*
*   $/Plasma20/Sources/Plasma/PubUtilLib/plVault/plVaultClientApi.cpp
*   
***/


#include "Pch.h"
#pragma hdrstop

#ifdef CLIENT

/*****************************************************************************
*
*   Private
*
***/

struct IVaultCallback {
	LINK(IVaultCallback)	link;
	VaultCallback *			cb;
};

struct INotifyAfterDownload : THashKeyVal<unsigned> {
	HASHLINK(INotifyAfterDownload)	link;
	unsigned						parentId;
	unsigned						childId;

	INotifyAfterDownload (unsigned parentId, unsigned childId)
	:	THashKeyVal<unsigned>(childId)
	,	parentId(parentId)
	,	childId(childId)
	{}
};

struct DeviceInbox : CHashKeyStr {
	HASHLINK(DeviceInbox)	link;
	wchar					inboxName[kMaxVaultNodeStringLength];

	DeviceInbox (const wchar device[], const wchar inbox[])
	:	CHashKeyStr(device)
	{
		StrCopy(inboxName, inbox, arrsize(inboxName));
	}
};

// A RelVaultNodeLink may be either stored in the global table,
// or stored in an IRelVaultNode's parents or children table.
struct RelVaultNodeLink : THashKeyVal<unsigned> {
	HASHLINK(RelVaultNodeLink)	link;
	RelVaultNode *				node;
	unsigned					ownerId;
	bool						seen;
	
	RelVaultNodeLink (bool seen, unsigned ownerId, unsigned nodeId, RelVaultNode * node)
	:	THashKeyVal<unsigned>(nodeId)
	,	seen(seen)
	,	ownerId(ownerId)
	,	node(node)
	{
		node->IncRef();
	}
	~RelVaultNodeLink () {
		node->DecRef();
	}
};


struct IRelVaultNode {
	RelVaultNode *	node;
	
	HASHTABLEDECL(
		RelVaultNodeLink,
		THashKeyVal<unsigned>,
		link
	) parents;

	HASHTABLEDECL(
		RelVaultNodeLink,
		THashKeyVal<unsigned>,
		link
	) children;

	IRelVaultNode (RelVaultNode * node);
	~IRelVaultNode ();

	// Unlink our node from all our parent and children
	void UnlinkFromRelatives ();
	
	// Unlink the node from our parent and children lists
	void Unlink (RelVaultNode * other);
};


struct VaultCreateNodeTrans {
	FVaultCreateNodeCallback	callback;
	void *						state;
	void *						param;
	
	unsigned					nodeId;
	RelVaultNode *				node;
	
	static void VaultNodeCreated (
		ENetError			result,
		void *				param,
		unsigned			nodeId
	);
	static void VaultNodeFetched (
		ENetError			result,
		void *				param,
		NetVaultNode *		node
	);
	
	void Complete (ENetError result);
};


struct VaultFindNodeTrans {
	FVaultFindNodeCallback		callback;
	void *						param;
	
	static void VaultNodeFound (
		ENetError			result,
		void *				param,
		unsigned			nodeIdCount,
		const unsigned		nodeIds[]
	);
};


struct VaultDownloadTrans {
	FVaultDownloadCallback		callback;
	void *						cbParam;
	FVaultProgressCallback		progressCallback;
	void *						cbProgressParam;
	
	wchar		tag[MAX_PATH];
	unsigned	nodeCount;
	unsigned	nodesLeft;
	unsigned	vaultId;
	ENetError	result;
	
	VaultDownloadTrans ();
	
	static void VaultNodeFetched (
		ENetError			result,
		void *				param,
		NetVaultNode *		node
	);
	static void VaultNodeRefsFetched (
		ENetError			result,
		void *				param,
		NetVaultNodeRef *	refs,
		unsigned			refCount
	);
};

struct VaultAgeInitTrans {
	FVaultInitAgeCallback	callback;
	void *					cbState;
	void *					cbParam;

	static void AgeInitCallback (
		ENetError		result,
		void *			param,
		unsigned		ageVaultId,
		unsigned		ageInfoVaultId
	);
};

struct AddChildNodeFetchTrans {
	FVaultAddChildNodeCallback	callback;
	void *						cbParam;
	ENetError					result;
	long						opCount;
	
	static void VaultNodeFetched (
		ENetError			result,
		void *				param,
		NetVaultNode *		node
	);
	static void VaultNodeRefsFetched (
		ENetError			result,
		void *				param,
		NetVaultNodeRef *	refs,
		unsigned			refCount
	);
};


/*****************************************************************************
*
*   Private data
*
***/

static bool	s_running;

static HASHTABLEDECL(
	RelVaultNodeLink,
	THashKeyVal<unsigned>,
	link
) s_nodes;

static LISTDECL(
	IVaultCallback,
	link
) s_callbacks;

static HASHTABLEDECL(
	INotifyAfterDownload,
	THashKeyVal<unsigned>,
	link
) s_notifyAfterDownload;

static HASHTABLEDECL(
	DeviceInbox,
	CHashKeyStr,
	link
) s_ageDeviceInboxes;

static bool s_processPlayerInbox = false;

/*****************************************************************************
*
*   Local functions
*
***/

static void VaultProcessVisitNote(RelVaultNode * rvnVisit);
static void VaultProcessUnvisitNote(RelVaultNode * rvnUnVisit);

static void VaultNodeFetched (
	ENetError			result,
	void *				param,
	NetVaultNode *		node
);
static void VaultNodeFound (
	ENetError			result,
	void *				param,
	unsigned			nodeIdCount,
	const unsigned		nodeIds[]
);

//============================================================================
static void VaultNodeAddedDownloadCallback(ENetError result, void * param) {
	unsigned childId = (unsigned)param;

	INotifyAfterDownload* notify = s_notifyAfterDownload.Find(childId);

	if (notify) {
		if (IS_NET_SUCCESS(result)) {
			RelVaultNodeLink* parentLink	= s_nodes.Find(notify->parentId);
			RelVaultNodeLink* childLink		= s_nodes.Find(notify->childId);

			if (parentLink && childLink) {
				if (childLink->node->nodeType == plVault::kNodeType_TextNote) {
					VaultTextNoteNode textNote(childLink->node);
					if (textNote.noteType == plVault::kNoteType_Visit)
						VaultProcessVisitNote(childLink->node);
					else if (textNote.noteType == plVault::kNoteType_UnVisit)
						VaultProcessUnvisitNote(childLink->node);
				}

				for (IVaultCallback * cb = s_callbacks.Head(); cb; cb = s_callbacks.Next(cb))
					cb->cb->AddedChildNode(parentLink->node, childLink->node);
			}
		}

		DEL(notify);
	}
}

//============================================================================
static void __cdecl LogDumpProc (
	void *				,
	const wchar			fmt[],
	...
) {
	va_list args;
	va_start(args, fmt);
	LogMsgV(kLogDebug, fmt, args);
	va_end(args);
}

//============================================================================
// Returns ids of nodes that had to be created (so we can fetch them)
static void BuildNodeTree (
	const NetVaultNodeRef	refs[],
	unsigned				refCount,
	ARRAY(unsigned) *		newNodeIds,
	ARRAY(unsigned) *		existingNodeIds,
	bool					notifyNow = true
) {
	for (unsigned i = 0; i < refCount; ++i) {
		// Find/Create global links
		RelVaultNodeLink * parentLink = s_nodes.Find(refs[i].parentId);
		if (!parentLink) {
			newNodeIds->Add(refs[i].parentId);
			parentLink = NEWZERO(RelVaultNodeLink)(false, 0, refs[i].parentId, NEWZERO(RelVaultNode));
			parentLink->node->nodeId = refs[i].parentId; // set directly so that the field's dirty flag isn't set
			s_nodes.Add(parentLink);
		}
		else {
			existingNodeIds->Add(refs[i].parentId);
		}
		RelVaultNodeLink * childLink = s_nodes.Find(refs[i].childId);
		if (!childLink) {
			newNodeIds->Add(refs[i].childId);
			childLink = NEWZERO(RelVaultNodeLink)(refs[i].seen, refs[i].ownerId, refs[i].childId, NEWZERO(RelVaultNode));
			childLink->node->nodeId = refs[i].childId; // set directly so that the field's dirty flag isn't set
			s_nodes.Add(childLink);
		}
		else {
			existingNodeIds->Add(refs[i].childId);
			if (unsigned ownerId = refs[i].ownerId)
				childLink->ownerId = ownerId;
		}

		RelVaultNode * parentNode = parentLink->node;
		RelVaultNode * childNode = childLink->node;
		
		bool isImmediateParent = parentNode->IsParentOf(refs[i].childId, 1);
		bool isImmediateChild = childNode->IsChildOf(refs[i].parentId, 1);
			
		if (!isImmediateParent) {
			// Add parent to child's parents table
			parentLink = NEWZERO(RelVaultNodeLink)(false, 0, parentNode->nodeId, parentNode);
			childNode->state->parents.Add(parentLink);
			LogMsg(kLogDebug, L"Added relationship: p:%u,c:%u", refs[i].parentId, refs[i].childId);
		}
		
		if (!isImmediateChild) {
			// Add child to parent's children table
			childLink = NEWZERO(RelVaultNodeLink)(refs[i].seen, refs[i].ownerId, childNode->nodeId, childNode);
			parentNode->state->children.Add(childLink);

			if (notifyNow || childNode->nodeType != 0) {
				// We made a new link, so make the callbacks
				for (IVaultCallback * cb = s_callbacks.Head(); cb; cb = s_callbacks.Next(cb))
					cb->cb->AddedChildNode(parentNode, childNode);
			}
			else {
				INotifyAfterDownload* notify = NEWZERO(INotifyAfterDownload)(parentNode->nodeId, childNode->nodeId);
				s_notifyAfterDownload.Add(notify);
			}
		}
	}
}

//============================================================================
static void InitFetchedNode (RelVaultNode * rvn) {

	switch (rvn->nodeType) {
		case plVault::kNodeType_SDL: {
			VaultSDLNode access(rvn);
			if (!access.sdlData || !access.sdlDataLen)
				access.InitStateDataRecord(access.sdlName);
		}
		break;
	}
}

//============================================================================
static void FetchRefOwners (
	NetVaultNodeRef *			refs,
	unsigned					refCount
) {
	ARRAY(unsigned)	ownerIds;
	{	for (unsigned i = 0; i < refCount; ++i)
			if (unsigned ownerId = refs[i].ownerId)
				ownerIds.Add(ownerId);
	}
	QSORT(unsigned, ownerIds.Ptr(), ownerIds.Count(), elem1 < elem2);
	RelVaultNode * templateNode = NEWZERO(RelVaultNode);
	templateNode->IncRef();
	templateNode->SetNodeType(plVault::kNodeType_PlayerInfo);
	{	unsigned prevId = 0;
		for (unsigned i = 0; i < ownerIds.Count(); ++i) {
			if (ownerIds[i] != prevId) {
				prevId = ownerIds[i];
				VaultPlayerInfoNode access(templateNode);
				access.SetPlayerId(refs[i].ownerId);
				if (RelVaultNode * rvn = VaultGetNodeIncRef(templateNode)) {
					rvn->DecRef();
					continue;
				}
				NetCliAuthVaultNodeFind(
					templateNode,
					VaultNodeFound,
					nil
				);
			}
		}
	}
	templateNode->DecRef();
}

//============================================================================
static void FetchNodesFromRefs (
	NetVaultNodeRef *			refs,
	unsigned					refCount,
	FNetCliAuthVaultNodeFetched	fetchCallback,
	void *						fetchParam,
	unsigned *					fetchCount
	
) {
	// On the side, start downloading PlayerInfo nodes of ref owners we don't already have locally
	FetchRefOwners(refs, refCount);

	*fetchCount = 0;
	
	ARRAY(unsigned) newNodeIds;
	ARRAY(unsigned) existingNodeIds;
	
	BuildNodeTree(refs, refCount, &newNodeIds, &existingNodeIds);

	ARRAY(unsigned)	nodeIds;
	nodeIds.Add(newNodeIds.Ptr(), newNodeIds.Count());
	nodeIds.Add(existingNodeIds.Ptr(), existingNodeIds.Count());
	QSORT(unsigned, nodeIds.Ptr(), nodeIds.Count(), elem1 < elem2);

	// Fetch the nodes that do not yet have a nodetype
	unsigned prevId = 0;
	{for (unsigned i = 0; i < nodeIds.Count(); ++i) {
		RelVaultNodeLink * link = s_nodes.Find(nodeIds[i]);
		if (link->node->nodeType != 0)
			continue;
		// filter duplicates
		if (link->node->nodeId == prevId)
			continue;
		prevId = link->node->nodeId;
		NetCliAuthVaultNodeFetch(
			nodeIds[i],
			fetchCallback,
			fetchParam
		);
		++(*fetchCount);
	}}
}

//============================================================================
static void VaultNodeFound (
	ENetError			result,
	void *				,
	unsigned			nodeIdCount,
	const unsigned		nodeIds[]
) {
	// TODO: Support some sort of optional transaction object/callback state
	
	// error?
	if (IS_NET_ERROR(result))
		return;

	for (unsigned i = 0; i < nodeIdCount; ++i) {
		
		// See if we already have this node
		if (RelVaultNodeLink * link = s_nodes.Find(nodeIds[i]))
			return;

		// Start fetching the node			
		NetCliAuthVaultNodeFetch(nodeIds[i], VaultNodeFetched, nil);
	}
}

//============================================================================
static void VaultNodeFetched (
	ENetError			result,
	void *				,
	NetVaultNode *		node
) {
	if (IS_NET_ERROR(result)) {
		LogMsg(kLogDebug, L"VaultNodeFetched failed: %u (%s)", result, NetErrorToString(result));
		return;
	}

	// Add to global node table
	RelVaultNodeLink * link = s_nodes.Find(node->nodeId);
	if (!link) {
		link = NEWZERO(RelVaultNodeLink)(false, 0, node->nodeId, NEWZERO(RelVaultNode));
		link->node->nodeId = node->nodeId; // set directly so that the field's dirty flag isn't set
		s_nodes.Add(link);
	}
	link->node->CopyFrom(node, NetVaultNode::kCopyOverwrite);
	InitFetchedNode(link->node);
	
	link->node->Print(L"Fetched", LogDumpProc, 0);
}

//============================================================================
static void ChangedVaultNodeFetched (
	ENetError			result,
	void *				param,
	NetVaultNode *		node
) {
	if (IS_NET_ERROR(result)) {
		LogMsg(kLogDebug, L"ChangedVaultNodeFetched failed: %u (%s)", result, NetErrorToString(result));
		return;
	}

	VaultNodeFetched(result, param, node);

	RelVaultNodeLink* savedLink = s_nodes.Find(node->nodeId);

	if (savedLink) {
		for (IVaultCallback * cb = s_callbacks.Head(); cb; cb = s_callbacks.Next(cb))
			cb->cb->ChangedNode(savedLink->node);
	}
}

//============================================================================
static void VaultNodeChanged (
	unsigned		nodeId,
	const Uuid &	revisionId
) {
	LogMsg(kLogDebug, L"Notify: Node changed: %u", nodeId);

	RelVaultNodeLink * link = s_nodes.Find(nodeId);

	// We don't have the node, so we don't care that it changed (we actually
	// shouldn't have been notified)
	if (!link) {
		LogMsg(kLogDebug, L"rcvd change notification for node %u, but node doesn't exist locally.", nodeId);
		return;
	}

	// We are the party responsible for the change, so we already have the
	// latest version of the node; no need to fetch it.
	if (link->node->revisionId == revisionId)
		return;

	// We have the node and we weren't the one that changed it, so fetch it.
	NetCliAuthVaultNodeFetch(
		nodeId,
		ChangedVaultNodeFetched,
		nil
	);
}

//============================================================================
static void VaultNodeAdded (
	unsigned		parentId,
	unsigned		childId,
	unsigned		ownerId
) {
	LogMsg(kLogDebug, L"Notify: Node added: p:%u,c:%u", parentId, childId);

	unsigned inboxId = 0;	
	if (RelVaultNode * rvnInbox = VaultGetPlayerInboxFolderIncRef()) {
		inboxId = rvnInbox->nodeId;
		rvnInbox->DecRef();
	}

	// Build the relationship locally
	NetVaultNodeRef refs[] = {
		{ parentId, childId, ownerId }
	};
	ARRAY(unsigned) newNodeIds;
	ARRAY(unsigned) existingNodeIds;
	
	BuildNodeTree(refs, arrsize(refs), &newNodeIds, &existingNodeIds, false);

	ARRAY(unsigned)	nodeIds;
	nodeIds.Add(newNodeIds.Ptr(), newNodeIds.Count());
	nodeIds.Add(existingNodeIds.Ptr(), existingNodeIds.Count());
	QSORT(unsigned, nodeIds.Ptr(), nodeIds.Count(), elem1 < elem2);

	// Fetch the nodes that do not yet have a nodetype
	unsigned prevId = 0;
	unsigned i = 0;
	{for (; i < nodeIds.Count(); ++i) {
		RelVaultNodeLink * link = s_nodes.Find(nodeIds[i]);
		if (link->node->nodeType != 0)
			continue;
		// filter duplicates
		if (link->node->nodeId == prevId)
			continue;
		prevId = link->node->nodeId;
		VaultDownload(
			L"NodeAdded",
			nodeIds[i],
			VaultNodeAddedDownloadCallback,
			(void*)nodeIds[i],
			nil,
			nil
		);
	}}
	
	if (parentId == inboxId) {
		if (i > 0)
			s_processPlayerInbox = true;
		else
			VaultProcessPlayerInbox();
	}

	// if the added element is already downloaded then send the callbacks now
	RelVaultNodeLink* parentLink	= s_nodes.Find(parentId);
	RelVaultNodeLink* childLink		= s_nodes.Find(childId);

	if (childLink->node->nodeType != 0) {
		for (IVaultCallback * cb = s_callbacks.Head(); cb; cb = s_callbacks.Next(cb))
			cb->cb->AddedChildNode(parentLink->node, childLink->node);
	}
}

//============================================================================
static void VaultNodeRemoved (
	unsigned		parentId,
	unsigned		childId
) {
	LogMsg(kLogDebug, L"Notify: Node removed: p:%u,c:%u", parentId, childId);
	for (;;) {
		// Unlink 'em locally, if we can
		RelVaultNodeLink * parentLink = s_nodes.Find(parentId);
		if (!parentLink)
			break;

		RelVaultNodeLink * childLink = s_nodes.Find(childId);
		if (!childLink)
			break;
			
		if (parentLink->node->IsParentOf(childId, 1)) {
			// We have the relationship, so make the callbacks
			for (IVaultCallback * cb = s_callbacks.Head(); cb; cb = s_callbacks.Next(cb))
				cb->cb->RemovingChildNode(parentLink->node, childLink->node);
		}
			
		parentLink->node->state->Unlink(childLink->node);
		childLink->node->state->Unlink(parentLink->node);
		break;
	}
}

//============================================================================
static void VaultNodeDeleted (
	unsigned		nodeId
) {
	LogMsg(kLogDebug, L"Notify: Node deleted: %u", nodeId);
	VaultCull(nodeId);
}

//============================================================================
static void SaveDirtyNodes () {
	// Save a max of 5Kb every quarter second
	static const unsigned kSaveUpdateIntervalMs		= 250;
	static const unsigned kMaxBytesPerSaveUpdate	= 5 * 1024;
	static unsigned s_nextSaveMs;
	unsigned currTimeMs = TimeGetMs() | 1;
	if (!s_nextSaveMs || signed(s_nextSaveMs - currTimeMs) <= 0) {
		s_nextSaveMs = (currTimeMs + kSaveUpdateIntervalMs) | 1;
		unsigned bytesWritten = 0;
		for (RelVaultNodeLink * link = s_nodes.Head(); link; link = s_nodes.Next(link)) {
			if (bytesWritten >= kMaxBytesPerSaveUpdate)
				break;
			if (link->node->dirtyFlags) {

				// Auth server needs the name of the sdl record
				if (link->node->nodeType == plVault::kNodeType_SDL)
					link->node->dirtyFlags |= VaultSDLNode::kSDLName;

				if (unsigned bytes = NetCliAuthVaultNodeSave(link->node, nil, nil)) {
					bytesWritten += bytes;
					link->node->Print(L"Saving", LogDumpProc, 0);
				}
			}
		}
	}
}

//============================================================================
static RelVaultNode * GetChildFolderNode (
	RelVaultNode *	parent,
	unsigned		folderType,
	unsigned		maxDepth
) {
	if (!parent)
		return nil;

	RelVaultNode * rvn = parent->GetChildFolderNodeIncRef(folderType, maxDepth);
	if (rvn)
		rvn->DecRef();

	return rvn;
}

//============================================================================
static RelVaultNode * GetChildPlayerInfoListNode (
	RelVaultNode *	parent,
	unsigned		folderType,
	unsigned		maxDepth
) {
	if (!parent)
		return nil;

	RelVaultNode * rvn = parent->GetChildPlayerInfoListNodeIncRef(folderType, maxDepth);
	if (rvn)
		rvn->DecRef();

	return rvn;
}


/*****************************************************************************
*
*   VaultCreateNodeTrans
*
***/

//============================================================================
void VaultCreateNodeTrans::VaultNodeCreated (
	ENetError			result,
	void *				param,
	unsigned			nodeId
) {
	VaultCreateNodeTrans * trans = (VaultCreateNodeTrans *)param;
	if (IS_NET_ERROR(result)) {
		trans->Complete(result);
	}
	else {
		trans->nodeId = nodeId;
		NetCliAuthVaultNodeFetch(
			nodeId,
			VaultCreateNodeTrans::VaultNodeFetched,
			trans
		);
	}
}

//============================================================================
void VaultCreateNodeTrans::VaultNodeFetched (
	ENetError			result,
	void *				param,
	NetVaultNode *		node
) {
	::VaultNodeFetched(result, param, node);

	VaultCreateNodeTrans * trans = (VaultCreateNodeTrans *)param;
	
	if (IS_NET_SUCCESS(result))
		trans->node = s_nodes.Find(node->nodeId)->node;
	else
		trans->node = nil;
	
	trans->Complete(result);
}

//============================================================================
void VaultCreateNodeTrans::Complete (ENetError result) {

	if (callback)
		callback(
			result,
			state,
			param,
			node
		);

	DEL(this);
}


/*****************************************************************************
*
*   VaultFindNodeTrans
*
***/

//============================================================================
void VaultFindNodeTrans::VaultNodeFound (
	ENetError			result,
	void *				param,
	unsigned			nodeIdCount,
	const unsigned		nodeIds[]
) {
	VaultFindNodeTrans * trans = (VaultFindNodeTrans*)param;
	if (trans->callback)
		trans->callback(
			result,
			trans->param,
			nodeIdCount,
			nodeIds
		);
	DEL(trans);
}


/*****************************************************************************
*
*   VaultDownloadTrans
*
***/

//============================================================================
VaultDownloadTrans::VaultDownloadTrans () {
	ASSERT(!nodeCount);	// must be alloced with
}

//============================================================================
void VaultDownloadTrans::VaultNodeFetched (
	ENetError			result,
	void *				param,
	NetVaultNode *		node
) {
	::VaultNodeFetched(result, param, node);

	VaultDownloadTrans * trans = (VaultDownloadTrans *)param;
	if (IS_NET_ERROR(result)) {
		trans->result = result;
		//LogMsg(kLogError, L"Error fetching node...most likely trying to fetch a nodeid of 0");
	}
	
	--trans->nodesLeft;
//	LogMsg(kLogDebug, L"(Download) %u of %u nodes fetched", trans->nodeCount - trans->nodesLeft, trans->nodeCount);
	
	if (trans->progressCallback) {
		trans->progressCallback(
			trans->nodeCount,
			trans->nodeCount - trans->nodesLeft,
			trans->cbProgressParam
		);
	}
	
	if (!trans->nodesLeft) {
		VaultDump(trans->tag, trans->vaultId, LogDumpProc);

		if (trans->callback)
			trans->callback(
				trans->result,
				trans->cbParam
			);

		DEL(trans);
	}
}

//============================================================================
void VaultDownloadTrans::VaultNodeRefsFetched (
	ENetError			result,
	void *				param,
	NetVaultNodeRef *	refs,
	unsigned			refCount
) {
	VaultDownloadTrans * trans = (VaultDownloadTrans *)param;
	
	if (IS_NET_ERROR(result)) {
		LogMsg(kLogDebug, L"VaultNodeRefsFetched failed: %u (%s)", result, NetErrorToString(result));
		trans->result		= result;
		trans->nodesLeft	= 0;
	}
	else {
		if (refCount) {
			FetchNodesFromRefs(
				refs,
				refCount,
				VaultDownloadTrans::VaultNodeFetched,
				param,
				&trans->nodeCount
			);
			trans->nodesLeft = trans->nodeCount;
		}
		else {
			// root node has no child heirarchy? Make sure we still d/l the root node if necessary.
			RelVaultNodeLink* rootNodeLink = s_nodes.Find(trans->vaultId);
			if (!rootNodeLink || rootNodeLink->node->nodeType == 0) {
				NetCliAuthVaultNodeFetch(
					trans->vaultId,
					VaultDownloadTrans::VaultNodeFetched,
					trans
				);
				trans->nodesLeft = 1;
			}
		}
	}

	// Make the callback now if there are no nodes to fetch, or if error
	if (!trans->nodesLeft) {
		if (trans->callback)
			trans->callback(
				trans->result,
				trans->cbParam
			);

		DEL(trans);
	}
}


/*****************************************************************************
*
*   VaultAgeInitTrans
*
***/

//============================================================================
void VaultAgeInitTrans::AgeInitCallback (
	ENetError		result,
	void *			param,
	unsigned		ageVaultId,
	unsigned		ageInfoVaultId
) {
	VaultAgeInitTrans * trans = (VaultAgeInitTrans *)param;

	if (trans->callback)
		trans->callback(
			result,
			trans->cbState,
			trans->cbParam,
			ageVaultId,
			ageInfoVaultId
		);
	
	DEL(trans);
}


/*****************************************************************************
*
*   AddChildNodeFetchTrans
*
***/

//============================================================================
void AddChildNodeFetchTrans::VaultNodeRefsFetched (
	ENetError			result,
	void *				param,
	NetVaultNodeRef *	refs,
	unsigned			refCount
) {
	AddChildNodeFetchTrans * trans = (AddChildNodeFetchTrans *)param;

	if (IS_NET_ERROR(result)) {
		trans->result		= result;
	}
	else {
		unsigned incFetchCount = 0;
		FetchNodesFromRefs(
			refs,
			refCount,
			AddChildNodeFetchTrans::VaultNodeFetched,
			param,
			&incFetchCount
		);
		AtomicAdd(&trans->opCount, incFetchCount);
	}

	// Make the callback now if there are no nodes to fetch, or if error
	AtomicAdd(&trans->opCount, -1);	
	if (!trans->opCount) {
		if (trans->callback)
			trans->callback(
				trans->result,
				trans->cbParam
			);
		DEL(trans);
	}
}

//============================================================================
void AddChildNodeFetchTrans::VaultNodeFetched (
	ENetError			result,
	void *				param,
	NetVaultNode *		node
) {
	::VaultNodeFetched(result, param, node);
	
	AddChildNodeFetchTrans * trans = (AddChildNodeFetchTrans *)param;
	
	if (IS_NET_ERROR(result))
		trans->result = result;
		
	AtomicAdd(&trans->opCount, -1);	
	if (!trans->opCount) {
		if (trans->callback)
			trans->callback(
				trans->result,
				trans->cbParam
			);
		DEL(trans);
	}
}


/*****************************************************************************
*
*   IRelVaultNode
*
***/

//============================================================================
IRelVaultNode::IRelVaultNode (RelVaultNode * node)
:	node(node)
{
}

//============================================================================
IRelVaultNode::~IRelVaultNode () {
	ASSERT(!parents.Head());
	ASSERT(!children.Head());
}

//============================================================================
void IRelVaultNode::UnlinkFromRelatives () {

	RelVaultNodeLink * link, * next;
	for (link = parents.Head(); link; link = next) {
		next = parents.Next(link);

		// We have the relationship, so make the callbacks
		for (IVaultCallback * cb = s_callbacks.Head(); cb; cb = s_callbacks.Next(cb))
			cb->cb->RemovingChildNode(link->node, this->node);

		link->node->state->Unlink(node);
	}
	for (link = children.Head(); link; link = next) {
		next = children.Next(link);
		link->node->state->Unlink(node);
	}
	
	ASSERT(!parents.Head());
	ASSERT(!children.Head());
}


//============================================================================
void IRelVaultNode::Unlink (RelVaultNode * other) {
	ASSERT(other != node);
	
	RelVaultNodeLink * link;
	if (nil != (link = parents.Find(other->nodeId))) {
		// make them non-findable in our parents table
		link->link.Unlink();
		// remove us from other's tables.
		link->node->state->Unlink(node);
		DEL(link);
	}
	if (nil != (link = children.Find(other->nodeId))) {
		// make them non-findable in our children table
		link->link.Unlink();
		// remove us from other's tables.
		link->node->state->Unlink(node);
		DEL(link);
	}
}

/*****************************************************************************
*
*   RelVaultNode
*
***/

//============================================================================
RelVaultNode::RelVaultNode () {
	state = NEWZERO(IRelVaultNode)(this);
}

//============================================================================
RelVaultNode::~RelVaultNode () {
	DEL(state);
}

//============================================================================
bool RelVaultNode::IsParentOf (unsigned childId, unsigned maxDepth) {
	if (nodeId == childId)
		return false;
	if (maxDepth == 0)
		return false;
	if (state->children.Find(childId))
		return true;
	RelVaultNodeLink * link = state->children.Head();
	for (; link; link = state->children.Next(link))
		if (link->node->IsParentOf(childId, maxDepth - 1))
			return true;
	return false;
}

//============================================================================
bool RelVaultNode::IsChildOf (unsigned parentId, unsigned maxDepth) {
	if (nodeId == parentId)
		return false;
	if (maxDepth == 0)
		return false;
	if (state->parents.Find(parentId))
		return true;
	RelVaultNodeLink * link = state->parents.Head();
	for (; link; link = state->parents.Next(link))
		if (link->node->IsChildOf(parentId, maxDepth - 1))
			return true;
	return false;
}

//============================================================================
void RelVaultNode::GetRootIds (ARRAY(unsigned) * nodeIds) {
	RelVaultNodeLink * link = state->parents.Head();
	if (!link) {
		nodeIds->Add(nodeId);
	}
	else {
		for (; link; link = state->parents.Next(link))
			link->node->GetRootIds(nodeIds);
	}
}

//============================================================================
unsigned RelVaultNode::RemoveChildNodes (unsigned maxDepth) {
	hsAssert(false, "eric, implement me.");
	return 0;
}

//============================================================================
void RelVaultNode::GetChildNodeIds (
	ARRAY(unsigned) *	nodeIds,
	unsigned			maxDepth
) {
	if (!maxDepth)
		return;
	RelVaultNodeLink * link = state->children.Head();
	for (; link; link = state->children.Next(link)) {
		nodeIds->Add(link->node->nodeId);
		link->node->GetChildNodeIds(nodeIds, maxDepth-1);
	}
}

//============================================================================
void RelVaultNode::GetParentNodeIds (
	ARRAY(unsigned) *	nodeIds,
	unsigned			maxDepth
) {
	if (!maxDepth)
		return;
	RelVaultNodeLink * link = state->parents.Head();
	for (; link; link = state->parents.Next(link)) {
		nodeIds->Add(link->node->nodeId);
		link->node->GetParentNodeIds(nodeIds, maxDepth-1);
	}
}


//============================================================================
RelVaultNode * RelVaultNode::GetParentNodeIncRef (
	NetVaultNode *		templateNode,
	unsigned			maxDepth
) {
	if (maxDepth == 0)
		return false;

	RelVaultNodeLink * link;
	link = state->parents.Head();
	for (; link; link = state->parents.Next(link)) {
		if (link->node->Matches(templateNode)) {
			link->node->IncRef("Found");
			return link->node;
		}
	}
	
	link = state->parents.Head();
	for (; link; link = state->parents.Next(link)) {
		if (RelVaultNode * node = link->node->GetParentNodeIncRef(templateNode, maxDepth-1))
			return node;
	}

	return nil;	
}

//============================================================================
RelVaultNode * RelVaultNode::GetChildNodeIncRef (
	NetVaultNode *		templateNode,
	unsigned			maxDepth
) {
	if (maxDepth == 0)
		return false;

	RelVaultNodeLink * link;
	link = state->children.Head();
	for (; link; link = state->children.Next(link)) {
		if (link->node->Matches(templateNode)) {
			link->node->IncRef("Found");
			return link->node;
		}
	}
	
	link = state->children.Head();
	for (; link; link = state->children.Next(link)) {
		if (RelVaultNode * node = link->node->GetChildNodeIncRef(templateNode, maxDepth-1))
			return node;
	}

	return nil;	
}

//============================================================================
RelVaultNode * RelVaultNode::GetChildNodeIncRef (
	unsigned			nodeType,
	unsigned			maxDepth
) {
	NetVaultNode * templateNode = NEWZERO(NetVaultNode);
	templateNode->IncRef();
	templateNode->SetNodeType(nodeType);
	RelVaultNode * result = GetChildNodeIncRef(templateNode, maxDepth);
	templateNode->DecRef();
	return result;
}

//============================================================================
RelVaultNode * RelVaultNode::GetChildFolderNodeIncRef (
	unsigned			folderType,
	unsigned			maxDepth
) {
	NetVaultNode * templateNode = NEWZERO(NetVaultNode);
	templateNode->IncRef();
	templateNode->SetNodeType(plVault::kNodeType_Folder);
	VaultFolderNode folder(templateNode);
	folder.SetFolderType(folderType);
	RelVaultNode * result = GetChildNodeIncRef(templateNode, maxDepth);
	templateNode->DecRef();
	return result;
}

//============================================================================
RelVaultNode * RelVaultNode::GetChildPlayerInfoListNodeIncRef (
	unsigned			folderType,
	unsigned			maxDepth
) {
	NetVaultNode * templateNode = NEWZERO(NetVaultNode);
	templateNode->IncRef();
	templateNode->SetNodeType(plVault::kNodeType_PlayerInfoList);
	VaultPlayerInfoListNode access(templateNode);
	access.SetFolderType(folderType);
	RelVaultNode * result = GetChildNodeIncRef(templateNode, maxDepth);
	templateNode->DecRef();
	return result;
}

//============================================================================
RelVaultNode * RelVaultNode::GetChildAgeInfoListNodeIncRef (
	unsigned			folderType,
	unsigned			maxDepth
) {
	NetVaultNode * templateNode = NEWZERO(NetVaultNode);
	templateNode->IncRef();
	templateNode->SetNodeType(plVault::kNodeType_AgeInfoList);
	VaultAgeInfoListNode access(templateNode);
	access.SetFolderType(folderType);
	RelVaultNode * result = GetChildNodeIncRef(templateNode, maxDepth);
	templateNode->DecRef();
	return result;
}

//============================================================================
void RelVaultNode::GetChildNodesIncRef (
	unsigned				maxDepth,
	ARRAY(RelVaultNode*) *	nodes
) {
	if (maxDepth == 0)
		return;

	RelVaultNodeLink * link;
	link = state->children.Head();
	for (; link; link = state->children.Next(link)) {
		nodes->Add(link->node);
		link->node->IncRef();
		link->node->GetChildNodesIncRef(
			maxDepth - 1,
			nodes
		);
	}
}

//============================================================================
void RelVaultNode::GetChildNodesIncRef (
	NetVaultNode *			templateNode,
	unsigned				maxDepth,
	ARRAY(RelVaultNode*) *	nodes
) {
	RelVaultNodeLink * link;
	link = state->children.Head();
	for (; link; link = state->children.Next(link)) {
		if (link->node->Matches(templateNode)) {
			nodes->Add(link->node);
			link->node->IncRef();
		}
		link->node->GetChildNodesIncRef(
			templateNode,
			maxDepth - 1,
			nodes
		);
	}
}

//============================================================================
void RelVaultNode::GetChildNodesIncRef (
	unsigned				nodeType,
	unsigned				maxDepth,
	ARRAY(RelVaultNode*) *	nodes
) {
	NetVaultNode * templateNode = NEWZERO(NetVaultNode);
	templateNode->IncRef();
	templateNode->SetNodeType(nodeType);
	GetChildNodesIncRef(
		templateNode,
		maxDepth,
		nodes
	);
	templateNode->DecRef();
}

//============================================================================
void RelVaultNode::GetChildFolderNodesIncRef (
	unsigned				folderType,
	unsigned				maxDepth,
	ARRAY(RelVaultNode*) *	nodes
) {
	NetVaultNode * templateNode = NEWZERO(NetVaultNode);
	templateNode->IncRef();
	templateNode->SetNodeType(plVault::kNodeType_Folder);
	VaultFolderNode fldr(templateNode);
	fldr.SetFolderType(folderType);
	GetChildNodesIncRef(
		templateNode,
		maxDepth,
		nodes
	);
	templateNode->DecRef();
}

//============================================================================
unsigned RelVaultNode::GetRefOwnerId (unsigned parentId) {
	// find our parents' link to us and return its ownerId
	if (RelVaultNodeLink * parentLink = state->parents.Find(parentId))
		if (RelVaultNodeLink * childLink = parentLink->node->state->children.Find(nodeId))
			return childLink->ownerId;
	return 0;
}

//============================================================================
bool RelVaultNode::BeenSeen (unsigned parentId) const {
	// find our parents' link to us and return its seen flag
	if (RelVaultNodeLink * parentLink = state->parents.Find(parentId))
		if (RelVaultNodeLink * childLink = parentLink->node->state->children.Find(nodeId))
			return childLink->seen;
	return true;
}

//============================================================================
void RelVaultNode::SetSeen (unsigned parentId, bool seen) {
	// find our parents' link to us and set its seen flag
	if (RelVaultNodeLink * parentLink = state->parents.Find(parentId))
		if (RelVaultNodeLink * childLink = parentLink->node->state->children.Find(nodeId))
			if (childLink->seen != seen) {
				childLink->seen = seen;
				NetCliAuthVaultSetSeen(parentId, nodeId, seen);
			}
}

//============================================================================
void RelVaultNode::Print (const wchar tag[], FStateDump dumpProc, unsigned level) {
	wchar str[1024];
	StrPrintf(
		str,
		arrsize(str),
		L"%s%*s%*s%u, %S",
		tag ? tag : L"",
		tag ? 1 : 0,
		" ",
		level * 2,
		" ",
		nodeId,
		plVault::NodeTypeStr(nodeType, false)
	);

	NetVaultNodeFieldArray fields(this);
	for (qword bit = 1; bit; bit <<= 1) {
		if (!(fieldFlags & bit))
			continue;
		if (bit > fieldFlags)
			break;
		
		StrPack(str, L", ", arrsize(str));
		StrPack(str, fields.GetFieldName(bit), arrsize(str));
		if (fields.GetFieldAddress(bit)) {
			StrPack(str, L"=", arrsize(str));
			const unsigned chars = StrLen(str);
			fields.GetFieldValueString_LCS(bit, str + chars, arrsize(str) - chars * sizeof(str[0]));
		}
	}
	
	dumpProc(nil, str);
}

//============================================================================
void RelVaultNode::PrintTree (FStateDump dumpProc, unsigned level) {
	Print(L"", dumpProc, level);
	for (RelVaultNodeLink * link = state->children.Head(); link; link = state->children.Next(link))
		link->node->PrintTree(dumpProc, level + 1);
}

//============================================================================
RelVaultNode * RelVaultNode::GetParentAgeLinkIncRef () {

	// this function only makes sense when called on age info nodes
	ASSERT(nodeType == plVault::kNodeType_AgeInfo);
	
	RelVaultNode * result = nil;
	
	NetVaultNode * templateNode = NEWZERO(NetVaultNode);
	templateNode->IncRef();
	templateNode->SetNodeType(plVault::kNodeType_AgeLink);

	// Get our parent AgeLink node	
	if (RelVaultNode * rvnLink = GetParentNodeIncRef(templateNode, 1)) {
		// Get the next AgeLink node in our parent tree
		result = rvnLink->GetParentNodeIncRef(templateNode, 3);
	}
	
	templateNode->DecRef();
	return result;		
}


/*****************************************************************************
*
*   Exports - Callbacks
*
***/

//============================================================================
void VaultRegisterCallback (VaultCallback * cb) {
	IVaultCallback * internal = NEW(IVaultCallback);
	internal->cb = cb;
	cb->internal = internal;
	s_callbacks.Link(internal);
}

//============================================================================
void VaultUnregisterCallback (VaultCallback * cb) {
	ASSERT(cb->internal);
	DEL(cb->internal);
	cb->internal = nil;
}


/*****************************************************************************
*
*   Exports - Initialize
*
***/

//============================================================================
void VaultInitialize () {
	s_running = true;
	
	NetCliAuthVaultSetRecvNodeChangedHandler(VaultNodeChanged);
	NetCliAuthVaultSetRecvNodeAddedHandler(VaultNodeAdded);
	NetCliAuthVaultSetRecvNodeRemovedHandler(VaultNodeRemoved);
	NetCliAuthVaultSetRecvNodeDeletedHandler(VaultNodeDeleted);
}

//============================================================================
void VaultDestroy () {
	s_running = false;

	NetCliAuthVaultSetRecvNodeChangedHandler(nil);
	NetCliAuthVaultSetRecvNodeAddedHandler(nil);
	NetCliAuthVaultSetRecvNodeRemovedHandler(nil);
	NetCliAuthVaultSetRecvNodeDeletedHandler(nil);

	VaultClearDeviceInboxMap();
	
	RelVaultNodeLink * next, * link = s_nodes.Head();
	for (; link; link = next) {
		next = s_nodes.Next(link);
		link->node->state->UnlinkFromRelatives();
		DEL(link);
	}
}

//============================================================================
void VaultUpdate () {
	SaveDirtyNodes();
}


/*****************************************************************************
*
*   Exports - Generic Vault Access
*
***/

//============================================================================
static RelVaultNode * GetNode (
	unsigned id
) {
	RelVaultNodeLink * link = s_nodes.Find(id);
	if (link)
		return link->node;
	return nil;
}

//============================================================================
static RelVaultNode * GetNode (
	NetVaultNode *	templateNode
) {
	ASSERT(templateNode);
	RelVaultNodeLink * link = s_nodes.Head();
	while (link) {
		if (link->node->Matches(templateNode))
			return link->node;
		link = s_nodes.Next(link);
	}
	return nil;
}

//============================================================================
RelVaultNode * VaultGetNodeIncRef (
	NetVaultNode *	templateNode
) {
	if (RelVaultNode * node = GetNode(templateNode)) {
		node->IncRef();
		return node;
	}
	return nil;
}

//============================================================================
RelVaultNode * VaultGetNodeIncRef (
	unsigned		nodeId
) {
	if (RelVaultNode * node = GetNode(nodeId)) {
		node->IncRef();
		return node;
	}
	return nil;
}

//============================================================================
RelVaultNode * VaultGetNodeIncRef (
	unsigned	nodeId,
	const char	reftag[]
) {
	if (RelVaultNodeLink * link = s_nodes.Find(nodeId)) {
		link->node->IncRef(reftag);
		return link->node;
	}
	return nil;
}

//============================================================================
void VaultAddChildNode (
	unsigned					parentId,
	unsigned					childId,
	unsigned					ownerId,
	FVaultAddChildNodeCallback	callback,
	void *						param
) {
	// Make sure we only do the callback once
	bool madeCallback = false;

	// Too much of the client relies on the assumption that the node will be immediately
	// associated with its parent.  THIS SUCKS, because there's no way to guarantee the
	// association won't be circular (the db checks this in a comprehensive way).
	// Because the client depends on this so much, we just link 'em together here if 
	// we have both of them present locally.
	// This directly affects: New clothing items added to the avatar outfit folder,
	// new chronicle entries in some ages, and I'm sure several other situations.

	if (RelVaultNodeLink * parentLink = s_nodes.Find(parentId)) {
		RelVaultNodeLink * childLink = s_nodes.Find(childId);
		if (!childLink) {
			childLink = NEWZERO(RelVaultNodeLink)(false, ownerId, childId, NEWZERO(RelVaultNode));
			childLink->node->nodeId = childId;	// set directly so that the field's dirty flag isn't set
			s_nodes.Add(childLink);
		}
		else if (ownerId) {
			childLink->ownerId = ownerId;
		}

		// We can do a sanity check for a would-be circular link, but it isn't
		// authoritative.  The db will prevent circular links from entering into
		// the persistent state, but because we are hacking in the association
		// before the authoritative check, we're risking the local client operating
		// on bad, possibly harmful vault state.  Not harmful in a national security
		// kinda way, but still harmful.
		if (parentLink->node->IsChildOf(childId, 255)) {
			LogMsg(kLogDebug, L"Node relationship would be circular: p:%u, c:%u", parentId, childId);
			// callback now with error code
			if (callback)
				callback(kNetErrCircularReference, param);
		}
		else if (childLink->node->IsParentOf(parentId, 255)) {
			LogMsg(kLogDebug, L"Node relationship would be circular: p:%u, c:%u", parentId, childId);
			// callback now with error code
			if (callback)
				callback(kNetErrCircularReference, param);
		}
		else {
			NetVaultNodeRef refs[] = {
				{ parentId, childId, ownerId }
			};

			ARRAY(unsigned)	newNodeIds;
			ARRAY(unsigned)	existingNodeIds;

			BuildNodeTree(refs, arrsize(refs), &newNodeIds, &existingNodeIds);
		
			if (!childLink->node->nodeType || !parentLink->node->nodeType) {
				// One or more nodes need to be fetched before the callback is made
				AddChildNodeFetchTrans * trans = NEWZERO(AddChildNodeFetchTrans);
				trans->callback = callback;
				trans->cbParam = param;
				if (!childLink->node->nodeType) {
					AtomicAdd(&trans->opCount, 1);
					NetCliAuthVaultNodeFetch(
						childId,
						AddChildNodeFetchTrans::VaultNodeFetched,
						trans
					);
					AtomicAdd(&trans->opCount, 1);
					NetCliAuthVaultFetchNodeRefs(
						childId,
						AddChildNodeFetchTrans::VaultNodeRefsFetched,
						trans
					);
				}
				if (!parentLink->node->nodeType) {
					AtomicAdd(&trans->opCount, 1);
					NetCliAuthVaultNodeFetch(
						parentId,
						AddChildNodeFetchTrans::VaultNodeFetched,
						trans
					);
					AtomicAdd(&trans->opCount, 1);
					NetCliAuthVaultFetchNodeRefs(
						parentId,
						AddChildNodeFetchTrans::VaultNodeRefsFetched,
						trans
					);
				}
			}
			else {
				// We have both nodes already, so make the callback now.
				if (callback) {
					callback(kNetSuccess, param);
					madeCallback = true;
				}
			}
		}
	}
	else {
		// Parent doesn't exist locally (and we may not want it to), just make the callback now.
		if (callback) {
			callback(kNetSuccess, param);
			madeCallback = true;
		}
	}

	// Send it on up to the vault. The db server filters out duplicate and
	// circular node relationships. We send the request up even if we think
	// the relationship would be circular since the db does a universal
	// check and is the only real authority in this matter.
	NetCliAuthVaultNodeAdd(
		parentId,
		childId,
		ownerId,
		madeCallback ? nil : callback,
		madeCallback ? nil : param
	);
}

//============================================================================
namespace _VaultAddChildNodeAndWait {

struct _AddChildNodeParam {
	ENetError		result;
	bool			complete;
};
static void _AddChildNodeCallback (
	ENetError		result,
	void *			vparam
) {
	_AddChildNodeParam * param = (_AddChildNodeParam *)vparam;
	param->result		= result;
	param->complete		= true;
}

} // namespace _VaultAddChildNodeAndWait

//============================================================================
void VaultAddChildNodeAndWait (
	unsigned					parentId,
	unsigned					childId,
	unsigned					ownerId
) {
	using namespace _VaultAddChildNodeAndWait;
	
	_AddChildNodeParam param;
	ZERO(param);
	
	VaultAddChildNode(
		parentId,
		childId,
		ownerId,
		_AddChildNodeCallback,
		&param
	);

	while (!param.complete) {
		NetClientUpdate();
		plgDispatch::Dispatch()->MsgQueueProcess();
		AsyncSleep(10);
	}
	
	if (IS_NET_ERROR(param.result))
		LogMsg(kLogError, L"VaultAddChildNodeAndWait: Failed to add child node: p:%u,c:%u. %s", parentId, childId, NetErrorToString(param.result));
}

//============================================================================
void VaultRemoveChildNode (
	unsigned						parentId,
	unsigned						childId,
	FVaultRemoveChildNodeCallback	callback,
	void *							param
) {
	for (;;) {
		// Unlink 'em locally, if we can
		RelVaultNodeLink * parentLink = s_nodes.Find(parentId);
		if (!parentLink)
			break;

		RelVaultNodeLink * childLink = s_nodes.Find(childId);
		if (!childLink)
			break;
			
		if (parentLink->node->IsParentOf(childId, 1)) {
			// We have the relationship, so make the callbacks
			for (IVaultCallback * cb = s_callbacks.Head(); cb; cb = s_callbacks.Next(cb))
				cb->cb->RemovingChildNode(parentLink->node, childLink->node);
		}
			
		parentLink->node->state->Unlink(childLink->node);
		childLink->node->state->Unlink(parentLink->node);
		break;
	}
	
	// Send it on up to the vault
	NetCliAuthVaultNodeRemove(
		parentId,
		childId,
		callback,
		param
	);
}

//============================================================================
void VaultSetNodeSeen (
	unsigned	nodeId,
	bool		seen
) {
	hsAssert(false, "eric, implement me");
}

//============================================================================
void VaultDeleteNode (
	unsigned	nodeId
) {
	// Send request up to vault.  We will remove it locally upon notification of deletion.
	NetCliAuthVaultNodeDelete(nodeId);	
}

//============================================================================
void VaultPublishNode (
	unsigned		nodeId,
	const wchar		deviceName[]
) {
	RelVaultNode * rvn;
	
	rvn = VaultAgeGetDeviceInboxIncRef(deviceName);
	if (!rvn) {
		LogMsg(kLogDebug, L"Failed to find inbox for device %s, adding it on-the-fly", deviceName);
		VaultAgeSetDeviceInboxAndWaitIncRef(deviceName, DEFAULT_DEVICE_INBOX);

		rvn = VaultAgeGetDeviceInboxIncRef(deviceName);
		if (!rvn) {
			LogMsg(kLogDebug, L"Failed to add inbox to device %s on-the-fly", deviceName);
			return;
		}
	}
	
	VaultAddChildNode(rvn->nodeId, nodeId, VaultGetPlayerId(), nil, nil);
	rvn->DecRef();
}

//============================================================================
void VaultSendNode (
	RelVaultNode*	srcNode,
	unsigned		dstPlayerId
) {
	NetCliAuthVaultNodeSave(srcNode, nil, nil);
	NetCliAuthVaultSendNode(srcNode->nodeId, dstPlayerId);
}

//============================================================================
void VaultCreateNode (
	NetVaultNode *				templateNode,
	FVaultCreateNodeCallback	callback,
	void *						state,
	void *						param
) {
	VaultCreateNodeTrans * trans = NEWZERO(VaultCreateNodeTrans);
	trans->callback	= callback;
	trans->state	= state;
	trans->param	= param;

	if (RelVaultNode * age = VaultGetAgeNodeIncRef()) {
		VaultAgeNode access(age);
		if (!(templateNode->fieldFlags & NetVaultNode::kCreateAgeName))
			templateNode->SetCreateAgeName(access.ageName);
		if (!(templateNode->fieldFlags & NetVaultNode::kCreateAgeUuid))
			templateNode->SetCreateAgeUuid(access.ageInstUuid);
		age->DecRef();
	}
	
	NetCliAuthVaultNodeCreate(
		templateNode,
		VaultCreateNodeTrans::VaultNodeCreated,
		trans
	);
}

//============================================================================
void VaultCreateNode (
	plVault::NodeTypes			nodeType,
	FVaultCreateNodeCallback	callback,
	void *						state,
	void *						param
) {
	RelVaultNode * templateNode = NEWZERO(RelVaultNode);
	templateNode->IncRef();
	templateNode->SetNodeType(nodeType);
	
	VaultCreateNode(
		templateNode,
		callback,
		state,
		param
	);

	templateNode->DecRef();
}

//============================================================================
namespace _VaultCreateNodeAndWaitIncRef {

struct _CreateNodeParam {
	RelVaultNode *	node;
	ENetError		result;
	bool			complete;
};
static void _CreateNodeCallback (
	ENetError		result,
	void *			,
	void *			vparam,
	RelVaultNode *	node
) {
	_CreateNodeParam * param = (_CreateNodeParam *)vparam;
	param->node		= node;
	param->result	= result;
	param->complete	= true;
}

} // namespace _VaultCreateNodeAndWaitIncRef

RelVaultNode * VaultCreateNodeAndWaitIncRef (
	NetVaultNode *				templateNode,
	ENetError *					result
) {
	using namespace _VaultCreateNodeAndWaitIncRef;
	
	_CreateNodeParam param;
	ZERO(param);
	
	VaultCreateNode(
		templateNode,
		_CreateNodeCallback,
		nil,
		&param
	);
	
	while (!param.complete) {
		NetClientUpdate();
		plgDispatch::Dispatch()->MsgQueueProcess();
		AsyncSleep(10);
	}
	
	*result = param.result;
	if (IS_NET_SUCCESS(param.result))
		param.node->IncRef();
	return param.node;
}

//============================================================================
RelVaultNode * VaultCreateNodeAndWaitIncRef (
	plVault::NodeTypes			nodeType,
	ENetError *					result
) {
	RelVaultNode * node;
	RelVaultNode * templateNode = NEWZERO(RelVaultNode);
	templateNode->IncRef();
	templateNode->SetNodeType(nodeType);
	
	node = VaultCreateNodeAndWaitIncRef(templateNode, result);

	templateNode->DecRef();
	return node;
}

//============================================================================
namespace _VaultForceSaveNodeAndWait {

struct _SaveNodeParam {
	ENetError		result;
	bool			complete;
};
static void _SaveNodeCallback (
	ENetError		result,
	void *			vparam
) {
	_SaveNodeParam * param = (_SaveNodeParam *)vparam;
	param->result	= result;
	param->complete	= true;
}

} // namespace _VaultForceSaveNodeAndWait

void VaultForceSaveNodeAndWait (
	NetVaultNode *		node
) {
	using namespace _VaultForceSaveNodeAndWait;
	
	_SaveNodeParam param;
	ZERO(param);
	
	NetCliAuthVaultNodeSave(
		node,
		_SaveNodeCallback,
		&param
	);
	
	while (!param.complete) {
		NetClientUpdate();
		plgDispatch::Dispatch()->MsgQueueProcess();
		AsyncSleep(10);
	}
}

//============================================================================
void VaultFindNodes (
	NetVaultNode *			templateNode,
	FVaultFindNodeCallback	callback,
	void *					param
) {
	VaultFindNodeTrans * trans = NEWZERO(VaultFindNodeTrans);
	trans->callback	= callback;
	trans->param	= param;

	NetCliAuthVaultNodeFind(
		templateNode,
		VaultFindNodeTrans::VaultNodeFound,
		trans
	);	
}

//============================================================================
namespace _VaultFindNodesAndWait {
	struct _FindNodeParam {
		ARRAY(unsigned)		nodeIds;
		ENetError			result;
		bool				complete;
	};
	static void _FindNodeCallback (
		ENetError			result,
		void *				vparam,
		unsigned			nodeIdCount,
		const unsigned		nodeIds[]
	) {
		_FindNodeParam * param = (_FindNodeParam *)vparam;
		param->nodeIds.Set(nodeIds, nodeIdCount);
		param->result	= result;
		param->complete	= true;
	}

} // namespace _VaultFindNodesAndWait

void VaultFindNodesAndWait (
	NetVaultNode *			templateNode,
	ARRAY(unsigned) *		nodeIds
) {
	using namespace _VaultFindNodesAndWait;
	
	_FindNodeParam	param;
	ZERO(param);
	
	NetCliAuthVaultNodeFind(
		templateNode,
		_FindNodeCallback,
		&param
	);

	while (!param.complete) {
		NetClientUpdate();
		plgDispatch::Dispatch()->MsgQueueProcess();
		AsyncSleep(10);
	}
	
	if (IS_NET_SUCCESS(param.result))
		nodeIds->Add(param.nodeIds.Ptr(), param.nodeIds.Count());
}

//============================================================================
void VaultLocalFindNodes (
	NetVaultNode *			templateNode,
	ARRAY(unsigned) *		nodeIds
) {
	for (RelVaultNodeLink * link = s_nodes.Head(); link != nil; link = s_nodes.Next(link)) {
		if (link->node->Matches(templateNode))
			nodeIds->Add(link->node->nodeId);
	}
}

//============================================================================
namespace _VaultFetchNodesAndWait {

	static void _VaultNodeFetched (
		ENetError			result,
		void *				param,
		NetVaultNode *		node
	) {
		::VaultNodeFetched(result, nil, node);
		
		long * nodeCount = (long *)param;
		AtomicAdd(nodeCount, -1);
	}

} // namespace _VaultFetchNodesAndWait

void VaultFetchNodesAndWait (
	const unsigned	nodeIds[],
	unsigned		count,
	bool			force
) {
	using namespace _VaultFetchNodesAndWait;
	
	long nodeCount = (long)count;
	
	for (unsigned i = 0; i < count; ++i) {
		
		if (!force) {
			// See if we already have this node
			if (RelVaultNodeLink * link = s_nodes.Find(nodeIds[i])) {
				AtomicAdd(&nodeCount, -1);
				continue;
			}
		}

		// Start fetching the node			
		NetCliAuthVaultNodeFetch(nodeIds[i], _VaultNodeFetched, (void *)&nodeCount);
	}

	while (nodeCount) {
		NetClientUpdate();
		AsyncSleep(10);
	}	
}


//============================================================================
void VaultInitAge (
	const plAgeInfoStruct * info,
	const Uuid &			parentAgeInstId,	// optional
	FVaultInitAgeCallback	callback,
	void *					state,
	void *					param
) {
	VaultAgeInitTrans * trans = NEWZERO(VaultAgeInitTrans);
	trans->callback			= callback;
	trans->cbState			= state;
	trans->cbParam			= param;
	
	wchar ageFilename[MAX_PATH];
	wchar ageInstName[MAX_PATH];
	wchar ageUserName[MAX_PATH];
	wchar ageDesc[1024];

	StrToUnicode(ageFilename, info->GetAgeFilename(), arrsize(ageFilename));
	StrToUnicode(ageInstName, info->GetAgeInstanceName(), arrsize(ageInstName));
	StrToUnicode(ageUserName, info->GetAgeUserDefinedName(), arrsize(ageUserName));
	StrToUnicode(ageDesc, info->GetAgeDescription(), arrsize(ageDesc));
	
	NetCliAuthVaultInitAge(
		(Uuid)*info->GetAgeInstanceGuid(),
		parentAgeInstId,
		ageFilename,
		ageInstName,
		ageUserName,
		ageDesc,
		info->GetAgeSequenceNumber(),
		info->GetAgeLanguage(),
		VaultAgeInitTrans::AgeInitCallback,
		trans
	);
}

/*****************************************************************************
*
*   Exports - Player Vault Access
*
***/

//============================================================================
static RelVaultNode * GetPlayerNode () {
	NetVaultNode * templateNode = NEWZERO(NetVaultNode);
	templateNode->IncRef();
	templateNode->SetNodeType(plVault::kNodeType_VNodeMgrPlayer);
	if (NetCommGetPlayer())
		templateNode->SetNodeId(NetCommGetPlayer()->playerInt);
	RelVaultNode * result = GetNode(templateNode);
	templateNode->DecRef();
	return result;
}

//============================================================================
unsigned VaultGetPlayerId () {
	if (RelVaultNode * rvn = GetPlayerNode())
		return rvn->nodeId;
	return 0;
}

//============================================================================
RelVaultNode * VaultGetPlayerNodeIncRef () {
	if (RelVaultNode * rvnPlr = GetPlayerNode()) {
		rvnPlr->IncRef();
		return rvnPlr;
	}
	return nil;
}

//============================================================================
RelVaultNode * VaultGetPlayerInfoNodeIncRef () {
	RelVaultNode * rvnPlr = VaultGetPlayerNodeIncRef();
	if (!rvnPlr)
		return nil;

	NetVaultNode * templateNode = NEWZERO(NetVaultNode);
	templateNode->IncRef();
	templateNode->SetNodeType(plVault::kNodeType_PlayerInfo);
	VaultPlayerInfoNode plrInfo(templateNode);
	plrInfo.SetPlayerId(rvnPlr->nodeId);
			
	rvnPlr->DecRef();

	RelVaultNode * result = nil;
	if (RelVaultNode * rvnPlrInfo = rvnPlr->GetChildNodeIncRef(templateNode, 1))
		result = rvnPlrInfo;

	templateNode->DecRef();
	
	return result;
}

//============================================================================
RelVaultNode * VaultGetAvatarOutfitFolderIncRef () {
	if (RelVaultNode * rvn = GetPlayerNode())
		return rvn->GetChildFolderNodeIncRef(plVault::kAvatarOutfitFolder, 1);
	return nil;
}

//============================================================================
RelVaultNode * VaultGetAvatarClosetFolderIncRef () {
	if (RelVaultNode * rvn = GetPlayerNode())
		return rvn->GetChildFolderNodeIncRef(plVault::kAvatarClosetFolder, 1);
	return nil;
}

//============================================================================
RelVaultNode * VaultGetChronicleFolderIncRef () {
	if (RelVaultNode * rvn = GetPlayerNode())
		return rvn->GetChildFolderNodeIncRef(plVault::kChronicleFolder, 1);
	return nil;
}

//============================================================================
RelVaultNode * VaultGetAgesIOwnFolderIncRef () {
	if (RelVaultNode * rvn = GetPlayerNode())
		return rvn->GetChildAgeInfoListNodeIncRef(plVault::kAgesIOwnFolder, 1);
	return nil;
}

//============================================================================
RelVaultNode * VaultGetAgesICanVisitFolderIncRef () {
	if (RelVaultNode * rvn = GetPlayerNode())
		return rvn->GetChildAgeInfoListNodeIncRef(plVault::kAgesICanVisitFolder, 1);
	return nil;
}

//============================================================================
RelVaultNode * VaultGetPlayerInboxFolderIncRef () {
	if (RelVaultNode * rvn = GetPlayerNode())
		return rvn->GetChildFolderNodeIncRef(plVault::kInboxFolder, 1);
	return nil;
}

//============================================================================
bool VaultGetLinkToMyNeighborhood (plAgeLinkStruct * link) {
	RelVaultNode * rvnFldr = VaultGetAgesIOwnFolderIncRef();
	if (!rvnFldr)
		return false;

	NetVaultNode * templateNode = NEWZERO(NetVaultNode);
	templateNode->IncRef();
	
	templateNode->SetNodeType(plVault::kNodeType_AgeInfo);
	VaultAgeInfoNode ageInfo(templateNode);
	wchar str[MAX_PATH];
	StrToUnicode(str, kNeighborhoodAgeFilename, arrsize(str));
	ageInfo.SetAgeFilename(str);

	RelVaultNode * node;
	if (nil != (node = rvnFldr->GetChildNodeIncRef(templateNode, 2))) {
		VaultAgeInfoNode info(node);
		info.CopyTo(link->GetAgeInfo());
		node->DecRef();
	}
	templateNode->DecRef();
	rvnFldr->DecRef();

	return node != nil;
}

//============================================================================
bool VaultGetLinkToMyPersonalAge (plAgeLinkStruct * link) {
	RelVaultNode * rvnFldr = VaultGetAgesIOwnFolderIncRef();
	if (!rvnFldr)
		return false;

	NetVaultNode * templateNode = NEWZERO(NetVaultNode);
	templateNode->IncRef();

	templateNode->SetNodeType(plVault::kNodeType_AgeInfo);
	VaultAgeInfoNode ageInfo(templateNode);
	wchar str[MAX_PATH];
	StrToUnicode(str, kPersonalAgeFilename, arrsize(str));
	ageInfo.SetAgeFilename(str);

	RelVaultNode * node;
	if (nil != (node = rvnFldr->GetChildNodeIncRef(templateNode, 2))) {
		VaultAgeInfoNode info(node);
		info.CopyTo(link->GetAgeInfo());
		node->DecRef();
	}
	templateNode->DecRef();
	rvnFldr->DecRef();

	return node != nil;
}

//============================================================================
bool VaultGetLinkToCity (plAgeLinkStruct * link) {
	RelVaultNode * rvnFldr = VaultGetAgesIOwnFolderIncRef();
	if (!rvnFldr)
		return false;

	NetVaultNode * templateNode = NEWZERO(NetVaultNode);
	templateNode->IncRef();
	templateNode->SetNodeType(plVault::kNodeType_AgeInfo);

	VaultAgeInfoNode ageInfo(templateNode);
	wchar str[MAX_PATH];
	StrToUnicode(str, kCityAgeFilename, arrsize(str));
	ageInfo.SetAgeFilename(str);

	RelVaultNode * node;
	if (nil != (node = rvnFldr->GetChildNodeIncRef(templateNode, 2))) {
		VaultAgeInfoNode info(node);
		info.CopyTo(link->GetAgeInfo());
		node->DecRef();
	}
	templateNode->DecRef();
	rvnFldr->DecRef();

	return node != nil;
}

//============================================================================
RelVaultNode * VaultGetOwnedAgeLinkIncRef (const plAgeInfoStruct * info) {
	
	RelVaultNode * rvnLink = nil;
	
	if (RelVaultNode * rvnFldr = VaultGetAgesIOwnFolderIncRef()) {

		NetVaultNode * templateNode = NEWZERO(NetVaultNode);
		templateNode->IncRef();
		templateNode->SetNodeType(plVault::kNodeType_AgeInfo);

		VaultAgeInfoNode ageInfo(templateNode);
		if (info->HasAgeFilename()) {
			wchar str[MAX_PATH];
			StrToUnicode(str, info->GetAgeFilename(), arrsize(str));
			ageInfo.SetAgeFilename(str);
		}
		if (info->HasAgeInstanceGuid()) {
			ageInfo.SetAgeInstGuid(*info->GetAgeInstanceGuid());
		}

		if (RelVaultNode * rvnInfo = rvnFldr->GetChildNodeIncRef(templateNode, 2)) {
			templateNode->fieldFlags = 0;
			templateNode->SetNodeType(plVault::kNodeType_AgeLink);	
			rvnLink = rvnInfo->GetParentNodeIncRef(templateNode, 1);
			rvnInfo->DecRef();
		}

		templateNode->DecRef();
		rvnFldr->DecRef();
	}
	
	return rvnLink;
}

//============================================================================
RelVaultNode * VaultGetOwnedAgeInfoIncRef (const plAgeInfoStruct * info) {
	
	RelVaultNode * rvnInfo = nil;
	
	if (RelVaultNode * rvnFldr = VaultGetAgesIOwnFolderIncRef()) {

		NetVaultNode * templateNode = NEWZERO(NetVaultNode);
		templateNode->IncRef();
		templateNode->SetNodeType(plVault::kNodeType_AgeInfo);

		VaultAgeInfoNode ageInfo(templateNode);
		if (info->HasAgeFilename()) {
			wchar str[MAX_PATH];
			StrToUnicode(str, info->GetAgeFilename(), arrsize(str));
			ageInfo.SetAgeFilename(str);
		}
		if (info->HasAgeInstanceGuid()) {
			ageInfo.SetAgeInstGuid(*info->GetAgeInstanceGuid());
		}

		rvnInfo = rvnFldr->GetChildNodeIncRef(templateNode, 2);

		templateNode->DecRef();
		rvnFldr->DecRef();
	}
	
	return rvnInfo;
}

//============================================================================
bool VaultGetOwnedAgeLink (const plAgeInfoStruct * info, plAgeLinkStruct * link) {
	bool result = false;
	if (RelVaultNode * rvnLink = VaultGetOwnedAgeLinkIncRef(info)) {
		if (RelVaultNode * rvnInfo = rvnLink->GetChildNodeIncRef(plVault::kNodeType_AgeInfo, 1)) {
			VaultAgeInfoNode ageInfo(rvnInfo);
			ageInfo.CopyTo(link->GetAgeInfo());
			rvnInfo->DecRef();
			result = true;
		}

		rvnLink->DecRef();
	}
	
	return result;
}

//============================================================================
bool VaultFindOrCreateChildAgeLinkAndWait (const wchar ownedAgeName[], const plAgeInfoStruct * info, plAgeLinkStruct * link) {
	hsAssert(false, "eric, implement me");
	return false;
}

//============================================================================
bool VaultAddOwnedAgeSpawnPoint (const Uuid & ageInstId, const plSpawnPointInfo & spawnPt) {
	
	RelVaultNode * fldr = nil;
	RelVaultNode * link = nil;
	
	for (;;) {
		if (!spawnPt.GetName())
			break;
		if (!spawnPt.GetTitle())
			break;
		if (!StrLen(spawnPt.GetName()))
			break;
		if (!StrLen(spawnPt.GetTitle()))
			break;

		fldr = VaultGetAgesIOwnFolderIncRef();
		if (!fldr)
			break;

		ARRAY(unsigned) nodeIds;
		fldr->GetChildNodeIds(&nodeIds, 1);
		
		NetVaultNode * templateNode = NEWZERO(NetVaultNode);
		templateNode->IncRef();
		templateNode->SetNodeType(plVault::kNodeType_AgeInfo);
		VaultAgeInfoNode access(templateNode);
		access.SetAgeInstGuid(ageInstId);
		
		for (unsigned i = 0; i < nodeIds.Count(); ++i) {
			link = VaultGetNodeIncRef(nodeIds[i]);
			if (!link)
				continue;
			if (RelVaultNode * info = link->GetChildNodeIncRef(templateNode, 1)) {
				VaultAgeLinkNode access(link);
				access.AddSpawnPoint(spawnPt);
				info->DecRef();
				link->DecRef();
				link = nil;
				break;
			}
		}
		templateNode->DecRef();

		break;	
	}		

	if (fldr)		
		fldr->DecRef();
	if (link)
		link->DecRef();
		
	return true;
}

//============================================================================
bool VaultSetOwnedAgePublicAndWait (const plAgeInfoStruct * info, bool publicOrNot) {
	if (RelVaultNode * rvnLink = VaultGetOwnedAgeLinkIncRef(info)) {
		if (RelVaultNode * rvnInfo = rvnLink->GetChildNodeIncRef(plVault::kNodeType_AgeInfo, 1)) {
			NetCliAuthSetAgePublic(rvnInfo->nodeId, publicOrNot);

			VaultAgeInfoNode access(rvnInfo);
			char ageName[MAX_PATH];
			StrToAnsi(ageName, access.ageFilename, arrsize(ageName));
			
			plVaultNotifyMsg * msg = NEWZERO(plVaultNotifyMsg);
			if (publicOrNot)
				msg->SetType(plVaultNotifyMsg::kPublicAgeCreated);
			else
				msg->SetType(plVaultNotifyMsg::kPublicAgeRemoved);
			msg->SetResultCode(true);
			msg->GetArgs()->AddString(plNetCommon::VaultTaskArgs::kAgeFilename, ageName);
			msg->Send();
			
			rvnInfo->DecRef();
		}
		rvnLink->DecRef();
	}
	return true;
}

//============================================================================
RelVaultNode * VaultGetVisitAgeLinkIncRef (const plAgeInfoStruct * info) {
	RelVaultNode * rvnLink = nil;
	
	if (RelVaultNode * rvnFldr = VaultGetAgesICanVisitFolderIncRef()) {

		NetVaultNode * templateNode = NEWZERO(NetVaultNode);
		templateNode->IncRef();
		templateNode->SetNodeType(plVault::kNodeType_AgeInfo);

		VaultAgeInfoNode ageInfo(templateNode);
		if (info->HasAgeFilename()) {
			wchar str[MAX_PATH];
			StrToUnicode(str, info->GetAgeFilename(), arrsize(str));
			ageInfo.SetAgeFilename(str);
		}
		if (info->HasAgeInstanceGuid()) {
			ageInfo.SetAgeInstGuid(*info->GetAgeInstanceGuid());
		}

		if (RelVaultNode * rvnInfo = rvnFldr->GetChildNodeIncRef(templateNode, 2)) {
			templateNode->fieldFlags = 0;
			templateNode->SetNodeType(plVault::kNodeType_AgeLink);	
			rvnLink = rvnInfo->GetParentNodeIncRef(templateNode, 1);
			rvnInfo->DecRef();
		}

		templateNode->DecRef();
		rvnFldr->DecRef();
	}
	
	return rvnLink;
}

//============================================================================
bool VaultGetVisitAgeLink (const plAgeInfoStruct * info, class plAgeLinkStruct * link) {
	RelVaultNode * rvn = VaultGetVisitAgeLinkIncRef(info);
	if (!rvn)
		return false;
		
	VaultAgeLinkNode ageLink(rvn);
	ageLink.CopyTo(link);
		
	rvn->DecRef();
	return true;
}

//============================================================================
namespace _VaultRegisterOwnedAgeAndWait {

struct _InitAgeParam {
	ENetError		result;
	bool			complete;
	unsigned		ageInfoId;
};
static void _InitAgeCallback (
	ENetError		result,
	void *			,
	void *			vparam,
	unsigned		ageVaultId,
	unsigned		ageInfoVaultId
) {
	_InitAgeParam * param = (_InitAgeParam *)vparam;
	param->ageInfoId	= ageInfoVaultId;
	param->result		= result;
	param->complete		= true;
}
struct _FetchVaultParam {
	ENetError		result;
	bool			complete;
};
static void _FetchVaultCallback (
	ENetError		result,
	void *			vparam
) {
	_FetchVaultParam * param = (_FetchVaultParam *)vparam;
	param->result		= result;
	param->complete		= true;
}
struct _CreateNodeParam {
	ENetError		result;
	bool			complete;
	unsigned		nodeId;
};
static void _CreateNodeCallback (
	ENetError		result,
	void *			,
	void *			vparam,
	RelVaultNode *	node
) {
	_CreateNodeParam * param = (_CreateNodeParam *)vparam;
	if (IS_NET_SUCCESS(result))
		param->nodeId = node->nodeId;
	param->result		= result;
	param->complete		= true;
}
struct _AddChildNodeParam {
	ENetError		result;
	bool			complete;
};
static void _AddChildNodeCallback (
	ENetError		result,
	void *			vparam
) {
	_AddChildNodeParam * param = (_AddChildNodeParam *)vparam;
	param->result		= result;
	param->complete		= true;
}

} // namespace _VaultRegisterOwnedAgeAndWait

//============================================================================
bool VaultRegisterOwnedAgeAndWait (const plAgeLinkStruct * link) {
	using namespace _VaultRegisterOwnedAgeAndWait;

	unsigned ageLinkId = 0;
	unsigned ageInfoId;
	unsigned agesIOwnId;
	
	bool result = false;
	
	for (;;) {
		if (RelVaultNode * rvn = VaultGetAgesIOwnFolderIncRef()) {
			agesIOwnId = rvn->nodeId;
			rvn->DecRef();
		}
		else {
			LogMsg(kLogError, L"RegisterOwnedAge: Failed to get player's AgesIOwnFolder");
			break;
		}

		// Check for existing link to this age	
		plAgeLinkStruct existing;
		if (VaultGetOwnedAgeLink(link->GetAgeInfo(), &existing)) {
			result = true;
			break;
		}
		
		{	// Init age vault
			_InitAgeParam	param;
			ZERO(param);

			VaultInitAge(
				link->GetAgeInfo(),
				kNilGuid,
				_InitAgeCallback,
				nil,
				&param
			);

			while (!param.complete) {
				NetClientUpdate();
				plgDispatch::Dispatch()->MsgQueueProcess();
				AsyncSleep(10);
			}
			
			if (IS_NET_ERROR(param.result)) {
				LogMsg(kLogError, L"RegisterOwnedAge: Failed to init age %S", link->GetAgeInfo()->GetAgeFilename());
				break;
			}
				
			ageInfoId = param.ageInfoId;
		}		
		
		{	// Create age link
			_CreateNodeParam	param;
			ZERO(param);

			VaultCreateNode(
				plVault::kNodeType_AgeLink,
				_CreateNodeCallback,
				nil,
				&param
			);

			while (!param.complete) {
				NetClientUpdate();
				plgDispatch::Dispatch()->MsgQueueProcess();
				AsyncSleep(10);
			}
			
			if (IS_NET_ERROR(param.result)) {
				LogMsg(kLogError, L"RegisterOwnedAge: Failed create age link node");
				break;
			}
				
			ageLinkId = param.nodeId;
		}		

		{	// Fetch age info node tree
			_FetchVaultParam	param;
			ZERO(param);
			
			VaultDownload(
				L"RegisterOwnedAge",
				ageInfoId,
				_FetchVaultCallback,
				&param,
				nil,
				nil
			);
			
			while (!param.complete) {
				NetClientUpdate();
				plgDispatch::Dispatch()->MsgQueueProcess();
				AsyncSleep(10);
			}
			
			if (IS_NET_ERROR(param.result)) {
				LogMsg(kLogError, L"RegisterOwnedAge: Failed to download age info vault");
				break;
			}
		}

		{ // Link:
			// ageLink to player's bookshelf folder
			// ageInfo to ageLink
			// playerInfo to ageOwners
			_AddChildNodeParam	param1;
			_AddChildNodeParam	param2;
			_AddChildNodeParam	param3;
			ZERO(param1);
			ZERO(param2);
			ZERO(param3);

			unsigned ageOwnersId = 0;		
			if (RelVaultNode * rvnAgeInfo = VaultGetNodeIncRef(ageInfoId)) {
				if (RelVaultNode * rvnAgeOwners = rvnAgeInfo->GetChildPlayerInfoListNodeIncRef(plVault::kAgeOwnersFolder, 1)) {
					ageOwnersId = rvnAgeOwners->nodeId;
					rvnAgeOwners->DecRef();
				}
				rvnAgeInfo->DecRef();
			}
			
			unsigned playerInfoId = 0;
			if (RelVaultNode * rvnPlayerInfo = VaultGetPlayerInfoNodeIncRef()) {
				playerInfoId = rvnPlayerInfo->nodeId;
				rvnPlayerInfo->DecRef();
			}

			VaultAddChildNode(
				agesIOwnId,
				ageLinkId,
				0,
				_AddChildNodeCallback,
				&param1
			);

			VaultAddChildNode(
				ageLinkId,
				ageInfoId,
				0,
				_AddChildNodeCallback,
				&param2
			);

			VaultAddChildNode(
				ageOwnersId,
				playerInfoId,
				0,
				_AddChildNodeCallback,
				&param3
			);

			while (!param1.complete && !param2.complete && !param3.complete) {
				NetClientUpdate();
				plgDispatch::Dispatch()->MsgQueueProcess();
				AsyncSleep(10);
			}
			
			if (IS_NET_ERROR(param1.result)) {
				LogMsg(kLogError, L"RegisterOwnedAge: Failed to add link to player's bookshelf");
				break;
			}
			if (IS_NET_ERROR(param2.result)) {
				LogMsg(kLogError, L"RegisterOwnedAge: Failed to add info to link");
				break;
			}
			if (IS_NET_ERROR(param3.result)) {
				LogMsg(kLogError, L"RegisterOwnedAge: Failed to add playerInfo to ageOwners");
				break;
			}
		}

		// Copy the link spawn point to the link node		
		if (RelVaultNode * node = VaultGetNodeIncRef(ageLinkId)) {
			VaultAgeLinkNode access(node);
			access.AddSpawnPoint(link->SpawnPoint());
			node->DecRef();
		}
		
		result = true;
		break;
	}
		
	plVaultNotifyMsg * msg = NEWZERO(plVaultNotifyMsg);
	msg->SetType(plVaultNotifyMsg::kRegisteredOwnedAge);
	msg->SetResultCode(result);
	msg->GetArgs()->AddInt(plNetCommon::VaultTaskArgs::kAgeLinkNode, ageLinkId);
	msg->Send();

	return result;
}

//============================================================================
namespace _VaultRegisterVisitAgeAndWait {

struct _InitAgeParam {
	ENetError		result;
	bool			complete;
	unsigned		ageInfoId;
};
static void _InitAgeCallback (
	ENetError		result,
	void *			,
	void *			vparam,
	unsigned		ageVaultId,
	unsigned		ageInfoVaultId
) {
	_InitAgeParam * param = (_InitAgeParam *)vparam;
	param->ageInfoId	= ageInfoVaultId;
	param->result		= result;
	param->complete		= true;
}
struct _FetchVaultParam {
	ENetError		result;
	bool			complete;
};
static void _FetchVaultCallback (
	ENetError		result,
	void *			vparam
) {
	_FetchVaultParam * param = (_FetchVaultParam *)vparam;
	param->result		= result;
	param->complete		= true;
}
struct _CreateNodeParam {
	ENetError		result;
	bool			complete;
	unsigned		nodeId;
};
static void _CreateNodeCallback (
	ENetError		result,
	void *			,
	void *			vparam,
	RelVaultNode *	node
) {
	_CreateNodeParam * param = (_CreateNodeParam *)vparam;
	if (IS_NET_SUCCESS(result))
		param->nodeId = node->nodeId;
	param->result		= result;
	param->complete		= true;
}
struct _AddChildNodeParam {
	ENetError		result;
	bool			complete;
};
static void _AddChildNodeCallback (
	ENetError		result,
	void *			vparam
) {
	_AddChildNodeParam * param = (_AddChildNodeParam *)vparam;
	param->result		= result;
	param->complete		= true;
}

} // namespace _VaultRegisterVisitAgeAndWait

//============================================================================
bool VaultRegisterVisitAgeAndWait (const plAgeLinkStruct * link) {
	using namespace _VaultRegisterVisitAgeAndWait;

	unsigned ageLinkId = 0;
	unsigned ageInfoId;
	unsigned agesICanVisitId;
	
	bool result = false;
	for (;;) {
		if (RelVaultNode * rvn = VaultGetAgesICanVisitFolderIncRef()) {
			agesICanVisitId = rvn->nodeId;
			rvn->DecRef();
		}
		else {
			LogMsg(kLogError, L"RegisterVisitAge: Failed to get player's AgesICanVisitFolder");
			break;
		}

		// Check for existing link to this age	
		plAgeLinkStruct existing;
		if (VaultGetVisitAgeLink(link->GetAgeInfo(), &existing)) {
			result = true;
			break;
		}
		
		
		{	// Init age vault
			_InitAgeParam	param;
			ZERO(param);

			VaultInitAge(
				link->GetAgeInfo(),
				kNilGuid,
				_InitAgeCallback,
				nil,
				&param
			);

			while (!param.complete) {
				NetClientUpdate();
				plgDispatch::Dispatch()->MsgQueueProcess();
				AsyncSleep(10);
			}
			
			if (IS_NET_ERROR(param.result)) {
				LogMsg(kLogError, L"RegisterVisitAge: Failed to init age %S", link->GetAgeInfo()->GetAgeFilename());
				break;
			}
				
			ageInfoId = param.ageInfoId;
		}		
		
		{	// Create age link
			_CreateNodeParam	param;
			ZERO(param);

			VaultCreateNode(
				plVault::kNodeType_AgeLink,
				_CreateNodeCallback,
				nil,
				&param
			);

			while (!param.complete) {
				NetClientUpdate();
				plgDispatch::Dispatch()->MsgQueueProcess();
				AsyncSleep(10);
			}
			
			if (IS_NET_ERROR(param.result)) {
				LogMsg(kLogError, L"RegisterVisitAge: Failed create age link node");
				break;
			}
				
			ageLinkId = param.nodeId;
		}		

		{	// Fetch age info node tree
			_FetchVaultParam	param;
			ZERO(param);
			
			VaultDownload(
				L"RegisterVisitAge",
				ageInfoId,
				_FetchVaultCallback,
				&param,
				nil,
				nil
			);
			
			while (!param.complete) {
				NetClientUpdate();
				plgDispatch::Dispatch()->MsgQueueProcess();
				AsyncSleep(10);
			}
			
			if (IS_NET_ERROR(param.result)) {
				LogMsg(kLogError, L"RegisterVisitAge: Failed to download age info vault");
				break;
			}
		}

		{ // Link:
			// ageLink to player's "can visit" folder
			// ageInfo to ageLink
			_AddChildNodeParam	param1;
			_AddChildNodeParam	param2;
			_AddChildNodeParam	param3;
			ZERO(param1);
			ZERO(param2);
			ZERO(param3);

			unsigned ageVisitorsId = 0;		
			if (RelVaultNode * rvnAgeInfo = VaultGetNodeIncRef(ageInfoId)) {
				if (RelVaultNode * rvnAgeVisitors = rvnAgeInfo->GetChildPlayerInfoListNodeIncRef(plVault::kCanVisitFolder, 1)) {
					ageVisitorsId = rvnAgeVisitors->nodeId;
					rvnAgeVisitors->DecRef();
				}
				rvnAgeInfo->DecRef();
			}
			
			unsigned playerInfoId = 0;
			if (RelVaultNode * rvnPlayerInfo = VaultGetPlayerInfoNodeIncRef()) {
				playerInfoId = rvnPlayerInfo->nodeId;
				rvnPlayerInfo->DecRef();
			}

			VaultAddChildNode(
				agesICanVisitId,
				ageLinkId,
				0,
				_AddChildNodeCallback,
				&param1
			);

			VaultAddChildNode(
				ageLinkId,
				ageInfoId,
				0,
				_AddChildNodeCallback,
				&param2
			);

			VaultAddChildNode(
				ageVisitorsId,
				playerInfoId,
				0,
				_AddChildNodeCallback,
				&param3
			);

			while (!param1.complete && !param2.complete && !param3.complete) {
				NetClientUpdate();
				plgDispatch::Dispatch()->MsgQueueProcess();
				AsyncSleep(10);
			}
			
			if (IS_NET_ERROR(param1.result)) {
				LogMsg(kLogError, L"RegisterVisitAge: Failed to add link to folder");
				break;
			}
			if (IS_NET_ERROR(param2.result)) {
				LogMsg(kLogError, L"RegisterVisitAge: Failed to add info to link");
				break;
			}
			if (IS_NET_ERROR(param3.result)) {
				LogMsg(kLogError, L"RegisterVisitAge: Failed to add playerInfo to canVisit folder");
				break;
			}
		}

		// Copy the link spawn point to the link node		
		if (RelVaultNode * node = VaultGetNodeIncRef(ageLinkId)) {
			VaultAgeLinkNode access(node);
			access.AddSpawnPoint(link->SpawnPoint());
			node->DecRef();
		}

		result = true;
		break;
	}

	plVaultNotifyMsg * msg = NEWZERO(plVaultNotifyMsg);
	msg->SetType(plVaultNotifyMsg::kRegisteredVisitAge);
	msg->SetResultCode(result);
	msg->GetArgs()->AddInt(plNetCommon::VaultTaskArgs::kAgeLinkNode, ageLinkId);
	msg->Send();

	return result;
}


//============================================================================
bool VaultUnregisterOwnedAgeAndWait (const plAgeInfoStruct * info) {

	unsigned ageLinkId = 0;
	unsigned agesIOwnId;

	bool result = false;
	for (;;) {	
		RelVaultNode * rvnLink = VaultGetOwnedAgeLinkIncRef(info);
		if (!rvnLink) {
			result = true;
			break;	// we aren't an owner of the age, just return true
		}

		if (RelVaultNode * rvn = VaultGetAgesIOwnFolderIncRef()) {
			agesIOwnId = rvn->nodeId;
			rvn->DecRef();
		}
		else {
			LogMsg(kLogError, L"UnregisterOwnedAge: Failed to get player's AgesIOwnFolder");
			break;	// something's wrong with the player vault, it doesn't have a required folder node
		}
			
		ageLinkId = rvnLink->nodeId;
		
		unsigned ageOwnersId = 0;
		if (RelVaultNode * rvnAgeInfo = rvnLink->GetChildNodeIncRef(plVault::kNodeType_AgeInfo, 1)) {
			if (RelVaultNode * rvnAgeOwners = rvnAgeInfo->GetChildPlayerInfoListNodeIncRef(plVault::kAgeOwnersFolder, 1)) {
				ageOwnersId = rvnAgeOwners->nodeId;
				rvnAgeOwners->DecRef();
			}
			rvnAgeInfo->DecRef();
		}
		
		unsigned playerInfoId = 0;
		if (RelVaultNode * rvnPlayerInfo = VaultGetPlayerInfoNodeIncRef()) {
			playerInfoId = rvnPlayerInfo->nodeId;
			rvnPlayerInfo->DecRef();
		}
		
		rvnLink->DecRef();

		// remove our playerInfo from the ageOwners folder
		VaultRemoveChildNode(ageOwnersId, playerInfoId, nil, nil);
		
		// remove the link from AgesIOwn folder	
		VaultRemoveChildNode(agesIOwnId, ageLinkId, nil, nil);

		// delete the link node since link nodes aren't shared with anyone else
	//	VaultDeleteNode(ageLinkId);

		result = true;
		break;
	}
	
	plVaultNotifyMsg * msg = NEWZERO(plVaultNotifyMsg);
	msg->SetType(plVaultNotifyMsg::kUnRegisteredOwnedAge);
	msg->SetResultCode(result);
	msg->GetArgs()->AddInt(plNetCommon::VaultTaskArgs::kAgeLinkNode, ageLinkId);
	msg->Send();
	
	return result;
}

//============================================================================
bool VaultUnregisterVisitAgeAndWait (const plAgeInfoStruct * info) {

	unsigned ageLinkId = 0;
	unsigned agesICanVisitId;
	
	bool result = false;
	for (;;) {
		RelVaultNode * rvnLink = VaultGetVisitAgeLinkIncRef(info);
		if (!rvnLink) {
			result = true;
			break;	// we aren't an owner of the age, just return true
		}

		if (RelVaultNode * rvn = VaultGetAgesICanVisitFolderIncRef()) {
			agesICanVisitId = rvn->nodeId;
			rvn->DecRef();
		}
		else {
			LogMsg(kLogError, L"UnregisterOwnedAge: Failed to get player's AgesICanVisitFolder");
			break;	// something's wrong with the player vault, it doesn't have a required folder node
		}
			
		ageLinkId = rvnLink->nodeId;

		unsigned ageVisitorsId = 0;
		if (RelVaultNode * rvnAgeInfo = rvnLink->GetChildNodeIncRef(plVault::kNodeType_AgeInfo, 1)) {
			if (RelVaultNode * rvnAgeVisitors = rvnAgeInfo->GetChildPlayerInfoListNodeIncRef(plVault::kCanVisitFolder, 1)) {
				ageVisitorsId = rvnAgeVisitors->nodeId;
				rvnAgeVisitors->DecRef();
			}
			rvnAgeInfo->DecRef();
		}
		
		unsigned playerInfoId = 0;
		if (RelVaultNode * rvnPlayerInfo = VaultGetPlayerInfoNodeIncRef()) {
			playerInfoId = rvnPlayerInfo->nodeId;
			rvnPlayerInfo->DecRef();
		}

		rvnLink->DecRef();

		// remove our playerInfo from the ageVisitors folder
		VaultRemoveChildNode(ageVisitorsId, playerInfoId, nil, nil);

		// remove the link from AgesICanVisit folder	
		VaultRemoveChildNode(agesICanVisitId, ageLinkId, nil, nil);
		
		// delete the link node since link nodes aren't shared with anyone else
	//	VaultDeleteNode(ageLinkId);
	
		result = true;
		break;
	}
	
	plVaultNotifyMsg * msg = NEWZERO(plVaultNotifyMsg);
	msg->SetType(plVaultNotifyMsg::kUnRegisteredVisitAge);
	msg->SetResultCode(result);
	msg->GetArgs()->AddInt(plNetCommon::VaultTaskArgs::kAgeLinkNode, ageLinkId);
	msg->Send();

	return result;
}

//============================================================================
RelVaultNode * VaultFindChronicleEntryIncRef (const wchar entryName[], int entryType) {

	RelVaultNode * result = nil;
	if (RelVaultNode * rvnFldr = GetChildFolderNode(GetPlayerNode(), plVault::kChronicleFolder, 1)) {
		NetVaultNode * templateNode = NEWZERO(NetVaultNode);
		templateNode->IncRef();
		templateNode->SetNodeType(plVault::kNodeType_Chronicle);
		VaultChronicleNode chrn(templateNode);
		chrn.SetEntryName(entryName);
		if (entryType >= 0)
			chrn.SetEntryType(entryType);
		if (RelVaultNode * rvnChrn = rvnFldr->GetChildNodeIncRef(templateNode, 255))
			result = rvnChrn;
		templateNode->DecRef();
	}		
	return result;
}

//============================================================================
bool VaultHasChronicleEntry (const wchar entryName[], int entryType) {
	if (RelVaultNode * rvn = VaultFindChronicleEntryIncRef(entryName, entryType)) {
		rvn->DecRef();
		return true;
	}
	return false;
}

//============================================================================
void VaultAddChronicleEntryAndWait (
	const wchar entryName[],
	int			entryType,
	const wchar entryValue[]
) {
	if (RelVaultNode * rvnChrn = VaultFindChronicleEntryIncRef(entryName, entryType)) {
		VaultChronicleNode chrnNode(rvnChrn);
		chrnNode.SetEntryValue(entryValue);
	}
	else if (RelVaultNode * rvnFldr = GetChildFolderNode(GetPlayerNode(), plVault::kChronicleFolder, 1)) {
		NetVaultNode * templateNode = NEWZERO(NetVaultNode);
		templateNode->IncRef();
		templateNode->SetNodeType(plVault::kNodeType_Chronicle);
		VaultChronicleNode chrnNode(templateNode);
		chrnNode.SetEntryName(entryName);
		chrnNode.SetEntryType(entryType);
		chrnNode.SetEntryValue(entryValue);
		ENetError result;
		if (RelVaultNode * rvnChrn = VaultCreateNodeAndWaitIncRef(templateNode, &result)) {
			VaultAddChildNode(rvnFldr->nodeId, rvnChrn->nodeId, 0, nil, nil);
			rvnChrn->DecRef();
		}
		templateNode->DecRef();
	}
}

//============================================================================
bool VaultAmIgnoringPlayer (unsigned playerId) {
	bool retval = false;
	if (RelVaultNode * rvnFldr = GetChildPlayerInfoListNode(GetPlayerNode(), plVault::kIgnoreListFolder, 1)) {
		rvnFldr->IncRef();

		NetVaultNode * templateNode = NEWZERO(NetVaultNode);
		templateNode->IncRef();
		templateNode->SetNodeType(plVault::kNodeType_PlayerInfo);
		VaultPlayerInfoNode pinfoNode(templateNode);
		pinfoNode.SetPlayerId(playerId);

		if (RelVaultNode * rvnPlayerInfo = rvnFldr->GetChildNodeIncRef(templateNode, 1)) {
			retval = true;
			rvnPlayerInfo->DecRef();
		}

		templateNode->DecRef();
		rvnFldr->DecRef();
	}

	return retval;
}

//============================================================================
unsigned VaultGetKILevel () {
	hsAssert(false, "eric, implement me");
	return pfKIMsg::kNanoKI;
}

//============================================================================
bool VaultGetCCRStatus () {
	bool retval = false;
	if (RelVaultNode * rvnSystem = VaultGetSystemNodeIncRef()) {
		VaultSystemNode sysNode(rvnSystem);
		retval = (sysNode.ccrStatus != 0);

		rvnSystem->DecRef();
	}

	return retval;
}

//============================================================================
bool VaultSetCCRStatus (bool online) {
	bool retval = false;
	if (RelVaultNode * rvnSystem = VaultGetSystemNodeIncRef()) {
		VaultSystemNode sysNode(rvnSystem);
		sysNode.SetCCRStatus(online ? 1 : 0);

		rvnSystem->DecRef();
		retval = true;
	}

	return retval;
}

//============================================================================
void VaultDump (const wchar tag[], unsigned vaultId, FStateDump dumpProc) {
	LogMsg(kLogDebug, L"<---- ID:%u, Begin Vault%*s%s ---->", vaultId, tag ? 1 : 0, L" ", tag);

	if (RelVaultNode * rvn = GetNode(vaultId))
		rvn->PrintTree(dumpProc, 0);

	LogMsg(kLogDebug, L"<---- ID:%u, End Vault%*s%s ---->", vaultId, tag ? 1 : 0, L" ", tag);
}

//============================================================================
void VaultDump (const wchar tag[], unsigned vaultId) {
	VaultDump (tag, vaultId, LogDumpProc);
}

//============================================================================
bool VaultAmInMyPersonalAge () {
	bool result = false;

	plAgeInfoStruct info;
	info.SetAgeFilename(kPersonalAgeFilename);

	if (RelVaultNode * rvnLink = VaultGetOwnedAgeLinkIncRef(&info)) {
		if (RelVaultNode * rvnInfo = rvnLink->GetChildNodeIncRef(plVault::kNodeType_AgeInfo, 1)) {
			VaultAgeInfoNode ageInfo(rvnInfo);

			if (RelVaultNode* currentAgeInfoNode = VaultGetAgeInfoNodeIncRef()) {
				VaultAgeInfoNode curAgeInfo(currentAgeInfoNode);

				if (ageInfo.ageInstUuid == curAgeInfo.ageInstUuid)
					result = true;

				currentAgeInfoNode->DecRef();
			}

			rvnInfo->DecRef();
		}

		rvnLink->DecRef();
	}

	return result;
}

//============================================================================
bool VaultAmInMyNeighborhoodAge () {
	bool result = false;

	plAgeInfoStruct info;
	info.SetAgeFilename(kNeighborhoodAgeFilename);

	if (RelVaultNode * rvnLink = VaultGetOwnedAgeLinkIncRef(&info)) {
		if (RelVaultNode * rvnInfo = rvnLink->GetChildNodeIncRef(plVault::kNodeType_AgeInfo, 1)) {
			VaultAgeInfoNode ageInfo(rvnInfo);

			if (RelVaultNode* currentAgeInfoNode = VaultGetAgeInfoNodeIncRef()) {
				VaultAgeInfoNode curAgeInfo(currentAgeInfoNode);

				if (ageInfo.ageInstUuid == curAgeInfo.ageInstUuid)
					result = true;

				currentAgeInfoNode->DecRef();
			}

			rvnInfo->DecRef();
		}

		rvnLink->DecRef();
	}

	return result;
}

//============================================================================
bool VaultAmOwnerOfCurrentAge () {
	bool result = false;

	if (RelVaultNode* currentAgeInfoNode = VaultGetAgeInfoNodeIncRef()) {
		VaultAgeInfoNode curAgeInfo(currentAgeInfoNode);

		char* ageFilename = StrDupToAnsi(curAgeInfo.ageFilename);

		plAgeInfoStruct info;
		info.SetAgeFilename(ageFilename);

		FREE(ageFilename);
		
		if (RelVaultNode * rvnLink = VaultGetOwnedAgeLinkIncRef(&info)) {

			if (RelVaultNode * rvnInfo = rvnLink->GetChildNodeIncRef(plVault::kNodeType_AgeInfo, 1)) {
				VaultAgeInfoNode ageInfo(rvnInfo);

				if (ageInfo.ageInstUuid == curAgeInfo.ageInstUuid)
					result = true;

				rvnInfo->DecRef();
			}

			rvnLink->DecRef();
		}

		currentAgeInfoNode->DecRef();
	}

	return result;
}

//============================================================================
bool VaultAmCzarOfCurrentAge () {
	hsAssert(false, "eric, implement me");
	return true;
}

//============================================================================
bool VaultAmOwnerOfAge (const Uuid & ageInstId) {
	hsAssert(false, "eric, implement me");
	return true;
}

//============================================================================
bool VaultAmCzarOfAge (const Uuid & ageInstId) {
//	hsAssert(false, "eric, implement me");
	return false;
}

//============================================================================
bool VaultRegisterMTStationAndWait (
	const wchar	stationName[],
	const wchar	linkBackSpawnPtObjName[]
) {
	plAgeInfoStruct info;
	info.SetAgeFilename(kCityAgeFilename);
	if (RelVaultNode * rvn = VaultGetOwnedAgeLinkIncRef(&info)) {
		char title[MAX_PATH], spawnPt[MAX_PATH];
		StrToAnsi(title, stationName, arrsize(title));
		StrToAnsi(spawnPt, linkBackSpawnPtObjName, arrsize(spawnPt));
		VaultAgeLinkNode link(rvn);
		link.AddSpawnPoint(plSpawnPointInfo(title, spawnPt));
		rvn->DecRef();
		return true;
	}
	return false;
}

//============================================================================
void VaultProcessVisitNote(RelVaultNode * rvnVisit) {
	if (RelVaultNode * rvnInbox = VaultGetPlayerInboxFolderIncRef()) {
		rvnVisit->IncRef();

		VaultTextNoteNode visitAcc(rvnVisit);
		plAgeLinkStruct link;
		if (visitAcc.GetVisitInfo(link.GetAgeInfo())) {
			// Add it to our "ages i can visit" folder
			VaultRegisterVisitAgeAndWait(&link);
		}
		// remove it from the inbox
		VaultRemoveChildNode(rvnInbox->nodeId, rvnVisit->nodeId, nil, nil);
		rvnVisit->DecRef();

		rvnInbox->DecRef();
	}
}

//============================================================================
void VaultProcessUnvisitNote(RelVaultNode * rvnUnVisit) {
	if (RelVaultNode * rvnInbox = VaultGetPlayerInboxFolderIncRef()) {
		rvnUnVisit->IncRef();
		VaultTextNoteNode unvisitAcc(rvnUnVisit);
		plAgeInfoStruct info;
		if (unvisitAcc.GetVisitInfo(&info)) {
			// Remove it from our "ages i can visit" folder
			VaultUnregisterVisitAgeAndWait(&info);
		}
		// remove it from the inbox
		VaultRemoveChildNode(rvnInbox->nodeId, rvnUnVisit->nodeId, nil, nil);
		rvnUnVisit->DecRef();

		rvnInbox->DecRef();
	}
}

//============================================================================
void VaultProcessPlayerInbox () {
	if (RelVaultNode * rvnInbox = VaultGetPlayerInboxFolderIncRef()) {
		{	// Process new visit requests
			ARRAY(RelVaultNode*) visits;
			RelVaultNode * templateNode = NEWZERO(RelVaultNode);
			templateNode->IncRef();
			templateNode->SetNodeType(plVault::kNodeType_TextNote);
			VaultTextNoteNode tmpAcc(templateNode);
			tmpAcc.SetNoteType(plVault::kNoteType_Visit);
			rvnInbox->GetChildNodesIncRef(templateNode, 1, &visits);
			templateNode->DecRef();
			
			for (unsigned i = 0; i < visits.Count(); ++i) {
				RelVaultNode * rvnVisit = visits[i];
				VaultTextNoteNode visitAcc(rvnVisit);
				plAgeLinkStruct link;
				if (visitAcc.GetVisitInfo(link.GetAgeInfo())) {
					// Add it to our "ages i can visit" folder
					VaultRegisterVisitAgeAndWait(&link);
				}
				// remove it from the inbox
				VaultRemoveChildNode(rvnInbox->nodeId, rvnVisit->nodeId, nil, nil);
				rvnVisit->DecRef();
			}
		}
		{	// Process new unvisit requests
			ARRAY(RelVaultNode*) unvisits;
			RelVaultNode * templateNode = NEWZERO(RelVaultNode);
			templateNode->IncRef();
			templateNode->SetNodeType(plVault::kNodeType_TextNote);
			VaultTextNoteNode tmpAcc(templateNode);
			tmpAcc.SetNoteType(plVault::kNoteType_UnVisit);
			rvnInbox->GetChildNodesIncRef(templateNode, 1, &unvisits);
			templateNode->DecRef();
			
			for (unsigned i = 0; i < unvisits.Count(); ++i) {
				RelVaultNode * rvnUnVisit = unvisits[i];
				VaultTextNoteNode unvisitAcc(rvnUnVisit);
				plAgeInfoStruct info;
				if (unvisitAcc.GetVisitInfo(&info)) {
					// Remove it from our "ages i can visit" folder
					VaultUnregisterVisitAgeAndWait(&info);
				}
				// remove it from the inbox
				VaultRemoveChildNode(rvnInbox->nodeId, rvnUnVisit->nodeId, nil, nil);
				rvnUnVisit->DecRef();
			}
		}
		
		rvnInbox->DecRef();
	}
}


/*****************************************************************************
*
*   Exports - Age Vault Access
*
***/

//============================================================================
static RelVaultNode * GetAgeNode () {
	NetVaultNode * templateNode = NEWZERO(NetVaultNode);
	templateNode->IncRef();
	templateNode->SetNodeType(plVault::kNodeType_VNodeMgrAge);
	if (NetCommGetAge())
		templateNode->SetNodeId(NetCommGetAge()->ageVaultId);
	RelVaultNode * result = GetNode(templateNode);
	templateNode->DecRef();
	return result;
}

//============================================================================
RelVaultNode * VaultGetAgeNodeIncRef () {
	RelVaultNode * result = nil;
	NetVaultNode * templateNode = NEWZERO(NetVaultNode);
	templateNode->IncRef();
	templateNode->SetNodeType(plVault::kNodeType_VNodeMgrAge);
	if (NetCommGetAge())
		templateNode->SetNodeId(NetCommGetAge()->ageVaultId);
	if (RelVaultNode * rvnAge = VaultGetNodeIncRef(templateNode))
		result = rvnAge;
	templateNode->DecRef();
	return result;
}

//============================================================================
static RelVaultNode * GetAgeInfoNode () {
	RelVaultNode * rvnAge = VaultGetAgeNodeIncRef();
	if (!rvnAge)
		return nil;

	RelVaultNode * result = nil;
	NetVaultNode * templateNode = NEWZERO(NetVaultNode);
	templateNode->IncRef();
	
	templateNode->SetNodeType(plVault::kNodeType_AgeInfo);
	templateNode->SetCreatorId(rvnAge->nodeId);
			
	if (RelVaultNode * rvnAgeInfo = rvnAge->GetChildNodeIncRef(templateNode, 1)) {
		rvnAgeInfo->DecRef();
		result = rvnAgeInfo;
	}
	
	templateNode->DecRef();
	rvnAge->DecRef();
	
	return result;
}

//============================================================================
RelVaultNode * VaultGetAgeInfoNodeIncRef () {
	RelVaultNode * rvnAge = VaultGetAgeNodeIncRef();
	if (!rvnAge)
		return nil;

	RelVaultNode * result = nil;
	NetVaultNode * templateNode = NEWZERO(NetVaultNode);
	templateNode->IncRef();
	templateNode->SetNodeType(plVault::kNodeType_AgeInfo);
	templateNode->SetCreatorId(rvnAge->nodeId);
			
	if (RelVaultNode * rvnAgeInfo = rvnAge->GetChildNodeIncRef(templateNode, 1))
		result = rvnAgeInfo;
	
	templateNode->DecRef();
	rvnAge->DecRef();

	return result;
}

//============================================================================
RelVaultNode * VaultGetAgeChronicleFolderIncRef () {
	if (RelVaultNode * rvn = GetAgeNode())
		return rvn->GetChildFolderNodeIncRef(plVault::kChronicleFolder, 1);
	return nil;
}

//============================================================================
RelVaultNode * VaultGetAgeDevicesFolderIncRef () {
	if (RelVaultNode * rvn = GetAgeNode())
		return rvn->GetChildFolderNodeIncRef(plVault::kAgeDevicesFolder, 1);
	return nil;
}

//============================================================================
RelVaultNode * VaultGetAgeAgeOwnersFolderIncRef () {
	if (RelVaultNode * rvn = GetAgeInfoNode())
		return rvn->GetChildPlayerInfoListNodeIncRef(plVault::kAgeOwnersFolder, 1);
	return nil;
}

//============================================================================
RelVaultNode * VaultGetAgeCanVisitFolderIncRef () {
	if (RelVaultNode * rvn = GetAgeInfoNode())
		return rvn->GetChildPlayerInfoListNodeIncRef(plVault::kCanVisitFolder, 1);
	return nil;
}

//============================================================================
RelVaultNode * VaultGetAgePeopleIKnowAboutFolderIncRef () {
	if (RelVaultNode * rvn = GetAgeNode())
		return rvn->GetChildPlayerInfoListNodeIncRef(plVault::kPeopleIKnowAboutFolder, 1);
	return nil;
}

//============================================================================
RelVaultNode * VaultGetAgeSubAgesFolderIncRef () {
	if (RelVaultNode * rvn = GetAgeNode())
		return rvn->GetChildAgeInfoListNodeIncRef(plVault::kSubAgesFolder, 1);
	return nil;
}

//============================================================================
RelVaultNode * VaultGetAgePublicAgesFolderIncRef () {
	hsAssert(false, "eric, implement me");
	return nil;
}

//============================================================================
RelVaultNode * VaultAgeGetBookshelfFolderIncRef () {
	if (RelVaultNode * rvn = GetAgeNode())
		return rvn->GetChildAgeInfoListNodeIncRef(plVault::kAgesIOwnFolder, 1);
	return nil;
}

//============================================================================
RelVaultNode * VaultFindAgeSubAgeLinkIncRef (const plAgeInfoStruct * info) {
	RelVaultNode * rvnLink = nil;
	
	if (RelVaultNode * rvnFldr = VaultGetAgeSubAgesFolderIncRef()) {

		NetVaultNode * templateNode = NEWZERO(NetVaultNode);
		templateNode->IncRef();
		templateNode->SetNodeType(plVault::kNodeType_AgeInfo);

		VaultAgeInfoNode ageInfo(templateNode);
		wchar str[MAX_PATH];
		StrToUnicode(str, info->GetAgeFilename(), arrsize(str));
		ageInfo.SetAgeFilename(str);

		if (RelVaultNode * rvnInfo = rvnFldr->GetChildNodeIncRef(templateNode, 2)) {
			templateNode->fieldFlags = 0;
			templateNode->SetNodeType(plVault::kNodeType_AgeLink);	
			rvnLink = rvnInfo->GetParentNodeIncRef(templateNode, 1);
			rvnInfo->DecRef();
		}

		templateNode->DecRef();
		rvnFldr->DecRef();
	}
	
	return rvnLink;
}

//============================================================================
RelVaultNode * VaultFindAgeChronicleEntryIncRef (const wchar entryName[], int entryType) {
	hsAssert(false, "eric, implement me");
	return nil;
}

//============================================================================
void VaultAddAgeChronicleEntry (
	const wchar entryName[],
	int			entryType,
	const wchar entryValue[]
) {
	hsAssert(false, "eric, implement me");
}

//============================================================================
RelVaultNode * VaultAgeAddDeviceAndWaitIncRef (const wchar deviceName[]) {
	if (RelVaultNode * existing = VaultAgeGetDeviceIncRef(deviceName))
		return existing;
		
	RelVaultNode * device = nil;
	RelVaultNode * folder = nil;
	
	for (;;) {
		folder = VaultGetAgeDevicesFolderIncRef();
		if (!folder)
			break;

		ENetError result;
		device = VaultCreateNodeAndWaitIncRef(plVault::kNodeType_TextNote, &result);
		if (!device)
			break;

		VaultTextNoteNode access(device);
		access.SetNoteType(plVault::kNoteType_Device);			
		access.SetNoteTitle(deviceName);
			
		VaultAddChildNodeAndWait(folder->nodeId, device->nodeId, 0);
		break;
	}

	if (folder)
		folder->DecRef();
		
	return device;
}

//============================================================================
void VaultAgeRemoveDevice (const wchar deviceName[]) {
	if (RelVaultNode * folder = VaultGetAgeDevicesFolderIncRef()) {
		NetVaultNode * templateNode = NEWZERO(NetVaultNode);
		templateNode->IncRef();
		templateNode->SetNodeType(plVault::kNodeType_TextNote);
		VaultTextNoteNode access(templateNode);
		access.SetNoteTitle(deviceName);
		if (RelVaultNode * device = folder->GetChildNodeIncRef(templateNode, 1)) {
			VaultRemoveChildNode(folder->nodeId, device->nodeId, nil, nil);
			device->DecRef();

			if (DeviceInbox * deviceInbox = s_ageDeviceInboxes.Find(CHashKeyStr(deviceName)))
				DEL(device);
		}
		templateNode->DecRef();
		folder->DecRef();
	}
}

//============================================================================
bool VaultAgeHasDevice (const wchar deviceName[]) {
	bool found = false;
	if (RelVaultNode * folder = VaultGetAgeDevicesFolderIncRef()) {
		NetVaultNode * templateNode = NEWZERO(NetVaultNode);
		templateNode->IncRef();
		templateNode->SetNodeType(plVault::kNodeType_TextNote);
		VaultTextNoteNode access(templateNode);
		access.SetNoteTitle(deviceName);
		if (RelVaultNode * device = folder->GetChildNodeIncRef(templateNode, 1)) {
			found = true;
			device->DecRef();
		}
		templateNode->DecRef();
		folder->DecRef();
	}
	return found;
}

//============================================================================
RelVaultNode * VaultAgeGetDeviceIncRef (const wchar deviceName[]) {
	RelVaultNode * result = nil;
	if (RelVaultNode * folder = VaultGetAgeDevicesFolderIncRef()) {
		NetVaultNode * templateNode = NEWZERO(NetVaultNode);
		templateNode->IncRef();
		templateNode->SetNodeType(plVault::kNodeType_TextNote);
		VaultTextNoteNode access(templateNode);
		access.SetNoteTitle(deviceName);
		if (RelVaultNode * device = folder->GetChildNodeIncRef(templateNode, 1))
			result = device;
		templateNode->DecRef();
		folder->DecRef();
	}
	return result;
}

//============================================================================
RelVaultNode * VaultAgeSetDeviceInboxAndWaitIncRef (const wchar deviceName[], const wchar inboxName[]) {
	DeviceInbox * devInbox = s_ageDeviceInboxes.Find(CHashKeyStr(deviceName));
	if (devInbox) {
		StrCopy(devInbox->inboxName, inboxName, arrsize(devInbox->inboxName));
	}
	else {
		devInbox = NEWZERO(DeviceInbox)(deviceName, inboxName);
		s_ageDeviceInboxes.Add(devInbox);
	}

	// if we found the inbox or its a global inbox then return here, otherwise if its the default inbox and
	// it wasn't found then continue on and create the inbox
	RelVaultNode * existing = VaultAgeGetDeviceInboxIncRef(deviceName);
	if (existing || StrCmp(inboxName, DEFAULT_DEVICE_INBOX) != 0)
		return existing;

	RelVaultNode * device = nil;
	RelVaultNode * inbox  = nil;
	
	for (;;) {
		device = VaultAgeGetDeviceIncRef(deviceName);
		if (!device)
			break;

		ENetError result;
		inbox = VaultCreateNodeAndWaitIncRef(plVault::kNodeType_Folder, &result);
		if (!inbox)
			break;
			
		VaultFolderNode access(inbox);
		access.SetFolderName(inboxName);
		access.SetFolderType(plVault::kDeviceInboxFolder);
					
		VaultAddChildNodeAndWait(device->nodeId, inbox->nodeId, 0);
		break;
	}

	return inbox;	 		
}

//============================================================================
RelVaultNode * VaultAgeGetDeviceInboxIncRef (const wchar deviceName[]) {
	RelVaultNode * result = nil;
	DeviceInbox * devInbox = s_ageDeviceInboxes.Find(CHashKeyStr(deviceName));

	if (devInbox)
	{
		RelVaultNode * parentNode = nil;
		const wchar * inboxName = nil;

		if (StrCmp(devInbox->inboxName, DEFAULT_DEVICE_INBOX) == 0) {
			parentNode = VaultAgeGetDeviceIncRef(deviceName);
		}
		else {
			parentNode = VaultGetGlobalInboxIncRef();
		}

		if (parentNode) {
			NetVaultNode * templateNode = NEWZERO(NetVaultNode);
			templateNode->IncRef();
			templateNode->SetNodeType(plVault::kNodeType_Folder);
			VaultFolderNode access(templateNode);
			access.SetFolderType(plVault::kDeviceInboxFolder);
			access.SetFolderName(devInbox->inboxName);
			result = parentNode->GetChildNodeIncRef(templateNode, 1);
			templateNode->DecRef();
			parentNode->DecRef();
		}
	}
	return result;
}

//============================================================================
void VaultClearDeviceInboxMap () {
	while (DeviceInbox * inbox = s_ageDeviceInboxes.Head()) {
		DEL(inbox);
	}
}

//============================================================================
bool VaultAgeGetAgeSDL (plStateDataRecord * out) {
	bool result = false;
	if (RelVaultNode * rvn = VaultGetAgeInfoNodeIncRef()) {
		if (RelVaultNode * rvnSdl = rvn->GetChildNodeIncRef(plVault::kNodeType_SDL, 1)) {
			VaultSDLNode sdl(rvnSdl);
			result = sdl.GetStateDataRecord(out, plSDL::kKeepDirty);
			if (!result) {
				sdl.InitStateDataRecord(sdl.sdlName);
				result = sdl.GetStateDataRecord(out, plSDL::kKeepDirty);
			}
			rvnSdl->DecRef();
		}
		rvn->DecRef();
	}
	return result;
}

//============================================================================
void VaultAgeUpdateAgeSDL (const plStateDataRecord * rec) {
	if (RelVaultNode * rvn = VaultGetAgeInfoNodeIncRef()) {
		if (RelVaultNode * rvnSdl = rvn->GetChildNodeIncRef(plVault::kNodeType_SDL, 1)) {
			VaultSDLNode sdl(rvnSdl);
			sdl.SetStateDataRecord(rec, plSDL::kDirtyOnly | plSDL::kTimeStampOnRead);
			rvnSdl->DecRef();
		}
		rvn->DecRef();
	}
}

//============================================================================
unsigned VaultAgeGetAgeTime () {
	hsAssert(false, "eric, implement me");
	return 0;
}

//============================================================================
RelVaultNode * VaultGetSubAgeLinkIncRef (const plAgeInfoStruct * info) {
	
	RelVaultNode * rvnLink = nil;
	
	if (RelVaultNode * rvnFldr = VaultGetAgeSubAgesFolderIncRef()) {

		NetVaultNode * templateNode = NEWZERO(NetVaultNode);
		templateNode->IncRef();
		templateNode->SetNodeType(plVault::kNodeType_AgeInfo);

		VaultAgeInfoNode ageInfo(templateNode);
		wchar str[MAX_PATH];
		StrToUnicode(str, info->GetAgeFilename(), arrsize(str));
		ageInfo.SetAgeFilename(str);

		if (RelVaultNode * rvnInfo = rvnFldr->GetChildNodeIncRef(templateNode, 2)) {
			templateNode->fieldFlags = 0;
			templateNode->SetNodeType(plVault::kNodeType_AgeLink);	
			rvnLink = rvnInfo->GetParentNodeIncRef(templateNode, 1);
			rvnInfo->DecRef();
		}

		templateNode->DecRef();
		rvnFldr->DecRef();
	}
	
	return rvnLink;
}

//============================================================================
bool VaultAgeGetSubAgeLink (const plAgeInfoStruct * info, plAgeLinkStruct * link) {
	bool result = false;
	if (RelVaultNode * rvnLink = VaultGetSubAgeLinkIncRef(info)) {
		if (RelVaultNode * rvnInfo = rvnLink->GetChildNodeIncRef(plVault::kNodeType_AgeInfo, 1)) {
			VaultAgeInfoNode ageInfo(rvnInfo);
			ageInfo.CopyTo(link->GetAgeInfo());
			rvnInfo->DecRef();
			result = true;
		}

		rvnLink->DecRef();
	}
	
	return result;
}

//============================================================================
namespace _VaultCreateSubAgeAndWait {

struct _InitAgeParam {
	ENetError		result;
	bool			complete;
	unsigned		ageInfoId;
};
static void _InitAgeCallback (
	ENetError		result,
	void *			,
	void *			vparam,
	unsigned		ageVaultId,
	unsigned		ageInfoVaultId
) {
	_InitAgeParam * param = (_InitAgeParam *)vparam;
	param->ageInfoId	= ageInfoVaultId;
	param->result		= result;
	param->complete		= true;
}
struct _FetchVaultParam {
	ENetError		result;
	bool			complete;
};
static void _FetchVaultCallback (
	ENetError		result,
	void *			vparam
) {
	_FetchVaultParam * param = (_FetchVaultParam *)vparam;
	param->result		= result;
	param->complete		= true;
}
struct _CreateNodeParam {
	ENetError		result;
	bool			complete;
	unsigned		nodeId;
};
static void _CreateNodeCallback (
	ENetError		result,
	void *			,
	void *			vparam,
	RelVaultNode *	node
) {
	_CreateNodeParam * param = (_CreateNodeParam *)vparam;
	if (IS_NET_SUCCESS(result))
		param->nodeId = node->nodeId;
	param->result		= result;
	param->complete		= true;
}
struct _AddChildNodeParam {
	ENetError		result;
	bool			complete;
};
static void _AddChildNodeCallback (
	ENetError		result,
	void *			vparam
) {
	_AddChildNodeParam * param = (_AddChildNodeParam *)vparam;
	param->result		= result;
	param->complete		= true;
}

} // namespace _VaultCreateSubAgeAndWait

//============================================================================
bool VaultAgeFindOrCreateSubAgeLinkAndWait (
	const plAgeInfoStruct * info,
	plAgeLinkStruct *		link,
	const Uuid &			parentAgeInstId
) {
	if (RelVaultNode * rvnLink = VaultFindAgeSubAgeLinkIncRef(info)) {
		VaultAgeLinkNode linkAcc(rvnLink);
		linkAcc.CopyTo(link);
		if (RelVaultNode * rvnInfo = rvnLink->GetChildNodeIncRef(plVault::kNodeType_AgeInfo, 1)) {
			VaultAgeInfoNode infoAcc(rvnInfo);
			infoAcc.CopyTo(link->GetAgeInfo());
			rvnInfo->DecRef();
			rvnLink->DecRef();
			return true;
		}
	}

	using namespace _VaultCreateSubAgeAndWait;

	unsigned subAgesId;
	unsigned ageInfoId;
	unsigned ageLinkId;
	
	if (RelVaultNode * rvnSubAges = VaultGetAgeSubAgesFolderIncRef()) {
		subAgesId = rvnSubAges->nodeId;
		rvnSubAges->DecRef();
	}
	else {
		LogMsg(kLogError, L"CreateSubAge: Failed to get ages's SubAges folder");
		return false;
	}
	
	{	// Init age vault
		_InitAgeParam	param;
		ZERO(param);
		
		VaultInitAge(
			info,
			parentAgeInstId,
			_InitAgeCallback,
			nil,
			&param
		);

		while (!param.complete) {
			NetClientUpdate();
			plgDispatch::Dispatch()->MsgQueueProcess();
			AsyncSleep(10);
		}
		
		if (IS_NET_ERROR(param.result)) {
			LogMsg(kLogError, L"CreateSubAge: Failed to init age %S", link->GetAgeInfo()->GetAgeFilename());
			return false;
		}
			
		ageInfoId = param.ageInfoId;
	}		
	
	{	// Create age link
		_CreateNodeParam	param;
		ZERO(param);

		VaultCreateNode(
			plVault::kNodeType_AgeLink,
			_CreateNodeCallback,
			nil,
			&param
		);

		while (!param.complete) {
			NetClientUpdate();
			plgDispatch::Dispatch()->MsgQueueProcess();
			AsyncSleep(10);
		}
		
		if (IS_NET_ERROR(param.result)) {
			LogMsg(kLogError, L"CreateSubAge: Failed create age link node");
			return false;
		}
			
		ageLinkId = param.nodeId;
	}		

	{	// Fetch age info node tree
		_FetchVaultParam	param;
		ZERO(param);
		
		VaultDownload(
			L"CreateSubAge",
			ageInfoId,
			_FetchVaultCallback,
			&param,
			nil,
			nil
		);
		
		while (!param.complete) {
			NetClientUpdate();
			plgDispatch::Dispatch()->MsgQueueProcess();
			AsyncSleep(10);
		}
		
		if (IS_NET_ERROR(param.result)) {
			LogMsg(kLogError, L"CreateSubAge: Failed to download age info vault");
			return false;
		}
	}

	{ // Link:
		// ageLink to ages's subages folder
		// ageInfo to ageLink
		_AddChildNodeParam	param1;
		_AddChildNodeParam	param2;
		ZERO(param1);
		ZERO(param2);

		VaultAddChildNode(
			subAgesId,
			ageLinkId,
			0,
			_AddChildNodeCallback,
			&param1
		);

		VaultAddChildNode(
			ageLinkId,
			ageInfoId,
			0,
			_AddChildNodeCallback,
			&param2
		);

		while (!param1.complete && !param2.complete) {
			NetClientUpdate();
			plgDispatch::Dispatch()->MsgQueueProcess();
			AsyncSleep(10);
		}
		
		if (IS_NET_ERROR(param1.result)) {
			LogMsg(kLogError, L"CreateSubAge: Failed to add link to ages's subages");
			return false;
		}
		if (IS_NET_ERROR(param2.result)) {
			LogMsg(kLogError, L"CreateSubAge: Failed to add info to link");
			return false;
		}
	}
		
	if (RelVaultNode * rvnLink = VaultGetNodeIncRef(ageLinkId)) {
		VaultAgeLinkNode linkAcc(rvnLink);
		linkAcc.CopyTo(link);
		rvnLink->DecRef();
	}

	if (RelVaultNode * rvnInfo = VaultGetNodeIncRef(ageInfoId)) {
		VaultAgeInfoNode infoAcc(rvnInfo);
		infoAcc.CopyTo(link->GetAgeInfo());
		rvnInfo->DecRef();
	}

	return true;
}


//============================================================================
namespace _VaultCreateChildAgeAndWait {

struct _InitAgeParam {
	ENetError		result;
	bool			complete;
	unsigned		ageInfoId;
};
static void _InitAgeCallback (
	ENetError		result,
	void *			,
	void *			vparam,
	unsigned		ageVaultId,
	unsigned		ageInfoVaultId
) {
	_InitAgeParam * param = (_InitAgeParam *)vparam;
	param->ageInfoId	= ageInfoVaultId;
	param->result		= result;
	param->complete		= true;
}
struct _FetchVaultParam {
	ENetError		result;
	bool			complete;
};
static void _FetchVaultCallback (
	ENetError		result,
	void *			vparam
) {
	_FetchVaultParam * param = (_FetchVaultParam *)vparam;
	param->result		= result;
	param->complete		= true;
}
struct _CreateNodeParam {
	ENetError		result;
	bool			complete;
	unsigned		nodeId;
};
static void _CreateNodeCallback (
	ENetError		result,
	void *			,
	void *			vparam,
	RelVaultNode *	node
) {
	_CreateNodeParam * param = (_CreateNodeParam *)vparam;
	if (IS_NET_SUCCESS(result))
		param->nodeId = node->nodeId;
	param->result		= result;
	param->complete		= true;
}
struct _AddChildNodeParam {
	ENetError		result;
	bool			complete;
};
static void _AddChildNodeCallback (
	ENetError		result,
	void *			vparam
) {
	_AddChildNodeParam * param = (_AddChildNodeParam *)vparam;
	param->result		= result;
	param->complete		= true;
}

} // namespace _VaultCreateChildAgeAndWait

//============================================================================
bool VaultAgeFindOrCreateChildAgeLinkAndWait (
	const wchar				parentAgeName[],
	const plAgeInfoStruct * info,
	plAgeLinkStruct *		link
) {
	using namespace _VaultCreateChildAgeAndWait;

	unsigned childAgesId;
	unsigned ageInfoId;
	unsigned ageLinkId;

	{	// Get id of child ages folder
		RelVaultNode * rvnAgeInfo = nil;
		if (parentAgeName) {
			char ansi[MAX_PATH];
			StrToAnsi(ansi, parentAgeName, arrsize(ansi));
			plAgeInfoStruct pinfo;
			pinfo.SetAgeFilename(ansi);
			if (RelVaultNode * rvnAgeLink = VaultGetOwnedAgeLinkIncRef(&pinfo)) {
				rvnAgeInfo = rvnAgeLink->GetChildNodeIncRef(plVault::kNodeType_AgeInfo, 1);
				rvnAgeLink->DecRef();
			}
		}
		else {
			rvnAgeInfo = VaultGetAgeInfoNodeIncRef();
		}
		
		if (!rvnAgeInfo) {
			LogMsg(kLogError, L"CreateChildAge: Failed to get ages's AgeInfo node");
			return false;
		}

		RelVaultNode * rvnChildAges;
		if (nil != (rvnChildAges = rvnAgeInfo->GetChildAgeInfoListNodeIncRef(plVault::kChildAgesFolder, 1))) {
			childAgesId = rvnChildAges->nodeId;
		}		
		else {
			rvnAgeInfo->DecRef();
			LogMsg(kLogError, L"CreateChildAge: Failed to get ages's ChildAges folder");
			return false;
		}
		rvnAgeInfo->DecRef();
		
		// Check for existing child age in folder
		RelVaultNode * rvnLink = nil;
		NetVaultNode * templateNode = NEWZERO(NetVaultNode);
		templateNode->IncRef();
		templateNode->SetNodeType(plVault::kNodeType_AgeInfo);

		VaultAgeInfoNode ageInfo(templateNode);
		wchar str[MAX_PATH];
		StrToUnicode(str, info->GetAgeFilename(), arrsize(str));
		ageInfo.SetAgeFilename(str);

		if (RelVaultNode * rvnInfo = rvnChildAges->GetChildNodeIncRef(templateNode, 2)) {
			templateNode->fieldFlags = 0;
			templateNode->SetNodeType(plVault::kNodeType_AgeLink);	
			rvnLink = rvnInfo->GetParentNodeIncRef(templateNode, 1);
			rvnInfo->DecRef();
		}

		templateNode->DecRef();
		rvnChildAges->DecRef();
		
		if (rvnLink) {
			VaultAgeLinkNode access(rvnLink);
			access.CopyTo(link);
			rvnLink->DecRef();
			return true;
		}
	}	

	{	// Init age vault
		_InitAgeParam	param;
		ZERO(param);

		Uuid parentAgeInstId;
		ZERO(parentAgeInstId);
		if (RelVaultNode * rvnAge = VaultGetAgeNodeIncRef()) {
			VaultAgeNode access(rvnAge);
			parentAgeInstId = access.ageInstUuid;
			rvnAge->DecRef();
		}
		
		VaultInitAge(
			info,
			parentAgeInstId,
			_InitAgeCallback,
			nil,
			&param
		);

		while (!param.complete) {
			NetClientUpdate();
			plgDispatch::Dispatch()->MsgQueueProcess();
			AsyncSleep(10);
		}
		
		if (IS_NET_ERROR(param.result)) {
			LogMsg(kLogError, L"CreateChildAge: Failed to init age %S", link->GetAgeInfo()->GetAgeFilename());
			return false;
		}
			
		ageInfoId = param.ageInfoId;
	}		
	
	{	// Create age link
		_CreateNodeParam	param;
		ZERO(param);

		VaultCreateNode(
			plVault::kNodeType_AgeLink,
			_CreateNodeCallback,
			nil,
			&param
		);

		while (!param.complete) {
			NetClientUpdate();
			plgDispatch::Dispatch()->MsgQueueProcess();
			AsyncSleep(10);
		}
		
		if (IS_NET_ERROR(param.result)) {
			LogMsg(kLogError, L"CreateChildAge: Failed create age link node");
			return false;
		}
			
		ageLinkId = param.nodeId;
	}		

	{	// Fetch age info node tree
		_FetchVaultParam	param;
		ZERO(param);
		
		VaultDownload(
			L"CreateChildAge",
			ageInfoId,
			_FetchVaultCallback,
			&param,
			nil,
			nil
		);
		
		while (!param.complete) {
			NetClientUpdate();
			plgDispatch::Dispatch()->MsgQueueProcess();
			AsyncSleep(10);
		}
		
		if (IS_NET_ERROR(param.result)) {
			LogMsg(kLogError, L"CreateChildAge: Failed to download age info vault");
			return false;
		}
	}

	{ // Link:
		// ageLink to ages's subages folder
		// ageInfo to ageLink
		_AddChildNodeParam	param1;
		_AddChildNodeParam	param2;
		ZERO(param1);
		ZERO(param2);

		VaultAddChildNode(
			childAgesId,
			ageLinkId,
			0,
			_AddChildNodeCallback,
			&param1
		);

		VaultAddChildNode(
			ageLinkId,
			ageInfoId,
			0,
			_AddChildNodeCallback,
			&param2
		);

		while (!param1.complete && !param2.complete) {
			NetClientUpdate();
			plgDispatch::Dispatch()->MsgQueueProcess();
			AsyncSleep(10);
		}
		
		if (IS_NET_ERROR(param1.result)) {
			LogMsg(kLogError, L"CreateChildAge: Failed to add link to ages's subages");
			return false;
		}
		if (IS_NET_ERROR(param2.result)) {
			LogMsg(kLogError, L"CreateChildAge: Failed to add info to link");
			return false;
		}
	}
		
	if (RelVaultNode * rvnLink = VaultGetNodeIncRef(ageLinkId)) {
		VaultAgeLinkNode linkAcc(rvnLink);
		linkAcc.CopyTo(link);
		rvnLink->DecRef();
	}

	if (RelVaultNode * rvnInfo = VaultGetNodeIncRef(ageInfoId)) {
		VaultAgeInfoNode infoAcc(rvnInfo);
		infoAcc.CopyTo(link->GetAgeInfo());
		rvnInfo->DecRef();
	}

	return true;
}


/*****************************************************************************
*
*   CCR Vault Access
*
***/

//============================================================================
void VaultCCRDumpPlayers() {
	hsAssert(false, "eric, implement me");
}


/*****************************************************************************
*
*   Exports - Vault download
*
***/

//============================================================================
void VaultDownload (
	const wchar					tag[],
	unsigned					vaultId,
	FVaultDownloadCallback		callback,
	void *						cbParam,
	FVaultProgressCallback		progressCallback,
	void *						cbProgressParam
) {
	VaultDownloadTrans * trans = NEWZERO(VaultDownloadTrans);
	StrCopy(trans->tag, tag, arrsize(trans->tag));
	trans->callback			= callback;
	trans->cbParam			= cbParam;
	trans->progressCallback	= progressCallback;
	trans->cbProgressParam	= cbProgressParam;
	trans->vaultId			= vaultId;

	NetCliAuthVaultFetchNodeRefs(
		vaultId,
		VaultDownloadTrans::VaultNodeRefsFetched,
		trans
	);
}

//============================================================================
struct _DownloadVaultParam {
	ENetError		result;
	bool			complete;
};
static void _DownloadVaultCallback (
	ENetError		result,
	void *			vparam
) {
	_DownloadVaultParam * param = (_DownloadVaultParam *)vparam;
	param->result		= result;
	param->complete		= true;
}

void VaultDownloadAndWait (
	const wchar					tag[],
	unsigned					vaultId,
	FVaultProgressCallback		progressCallback,
	void *						cbProgressParam
) {
	_DownloadVaultParam	param;
	ZERO(param);
	
	VaultDownload(
		tag,
		vaultId,
		_DownloadVaultCallback,
		&param,
		progressCallback,
		cbProgressParam
	);
	
	while (!param.complete) {
		NetClientUpdate();
		plgDispatch::Dispatch()->MsgQueueProcess();
		AsyncSleep(10);
	}
}

//============================================================================
void VaultCull (unsigned vaultId) {
	// Remove the node from the global table
	if (RelVaultNodeLink * link = s_nodes.Find(vaultId)) {
		LogMsg(kLogDebug, L"Vault: Culling node %u", link->node->nodeId);
		link->node->state->UnlinkFromRelatives();
		DEL(link);
	}

	// Remove all orphaned nodes from the global table
	for (RelVaultNodeLink * next, * link = s_nodes.Head(); link; link = next) {
		next = s_nodes.Next(link);

		if (link->node->nodeType > plVault::kNodeType_VNodeMgrLow && link->node->nodeType < plVault::kNodeType_VNodeMgrHigh)
			continue;

		ARRAY(unsigned)	nodeIds;
		link->node->GetRootIds(&nodeIds);
		bool foundRoot = false;
		for (unsigned i = 0; i < nodeIds.Count(); ++i) {
			RelVaultNodeLink * root = s_nodes.Find(nodeIds[i]);
			if (root && root->node->nodeType > plVault::kNodeType_VNodeMgrLow && root->node->nodeType < plVault::kNodeType_VNodeMgrHigh) {
				foundRoot = true;
				break;
			}
		}
		if (!foundRoot) {
			LogMsg(kLogDebug, L"Vault: Culling node %u", link->node->nodeId);
			link->node->state->UnlinkFromRelatives();
			DEL(link);
		}
	}	
}

/*****************************************************************************
*
*   Exports - Vault global node handling
*
***/

//============================================================================
RelVaultNode * VaultGetSystemNodeIncRef () {
	RelVaultNode * result = nil;
	if (RelVaultNode * player = VaultGetPlayerNodeIncRef()) {
		NetVaultNode * templateNode = NEWZERO(NetVaultNode);
		templateNode->IncRef();
		templateNode->SetNodeType(plVault::kNodeType_System);
		if (RelVaultNode * systemNode = player->GetChildNodeIncRef(templateNode, 1))
			result = systemNode;
		templateNode->DecRef();
		player->DecRef();
	}
	return result;
}

//============================================================================
RelVaultNode * VaultGetGlobalInboxIncRef () {
	RelVaultNode * result = nil;
	if (RelVaultNode * system = VaultGetSystemNodeIncRef()) {
		NetVaultNode * templateNode = NEWZERO(NetVaultNode);
		templateNode->IncRef();
		templateNode->SetNodeType(plVault::kNodeType_Folder);
		VaultFolderNode folder(templateNode);
		folder.SetFolderType(plVault::kGlobalInboxFolder);
		if (RelVaultNode * inbox = system->GetChildNodeIncRef(templateNode, 1))
			result = inbox;
		templateNode->DecRef();
		system->DecRef();
	}
	return result;
}

#endif // def CLIENT