
/*	Copyright (C) G. Jespersen
	See Licence.txt.*/

#include "stdafx.h"
#include "MediaDefs.h"
#include "DsLavSplit.h"
#include "StdioFileEx.h"

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

///////////////////////////////////////////////////////////////////
// CTopicTree
CTopicTree::CTopicTree()
{
	Zero();
}
CTopicTree::~CTopicTree()
{
}
CTopicTree::CTopicTree(const CTopicTree & tree) 
{ 
	Copy(tree); 
}
void CTopicTree::operator=(const CTopicTree & tree) 
{ 
	Copy(tree); 
};

BOOL CTopicTree::operator==(const CTopicTree & tree)
{
	if(m_topicTreeAr == tree.m_topicTreeAr)
		return TRUE;
	return FALSE;
}

BOOL CTopicTree::operator!=(const CTopicTree & tree) 
{ 
	return !(*this == tree); 
}
void CTopicTree::Copy(const CTopicTree & tree)
{
	m_topicTreeAr.Copy(tree.m_topicTreeAr);
}
	
void CTopicTree::Zero() 
{
	m_topicTreeAr.RemoveAll();

	// Used for param in GetTopicRef().
	CNodeItem item;
	item.m_topicName = STR_ERROR;
	item.m_arIndex = 0;
	m_errTopic.m_nodesAr.Add(item);
}

// Add a CMediaItem to the topic tree. Note items are
// organized in the tree as per the Media Item's nodes
// list and MEDIA_TYPE.
BOOL CTopicTree::AddMediaItemToTree(CMediaItem & item, BOOL setResultsTopic)
{
	// Must have a type.
	MEDIA_TYPE type = item.m_mediaType;
	if(type == NONE)
		return FALSE;

	// Get number of topic nodes.
	int nodesListSz = item.m_nodesList.GetSize();

	// If no topic nodes, use default per type.
	if(!nodesListSz)
	{
		CString strNode = item.GetDefaultMediaTopicName();
		item.m_nodesList.Add(strNode);
		nodesListSz = 1;
	}
	// Get the MediaItems topic nodes into a CNodeArray.
	// This will build and add to the TopicTree any 
	// missing topics.
	CNodeArray nodeAr;
	if(!GetCreateTopicNodes(item.m_nodesList, item.m_mediaType, nodeAr))
	{
		TRACE(_T("CTopicTree::AddMediaItemToTree GetCreateTopicNodes failed.\n"));
		return FALSE;
	}
	// Get topic ref - note m_errTopic initialized in Zero().
	CTopic & topic = GetTopicRef(nodeAr, m_errTopic);

	// If fail errTopic was returned.
	if(topic.GetTopicName() == STR_ERROR)
	{
		TRACE(_T("CTopicTree::AddMediaItemToTree GetTopicRef failed.\n"));
		return FALSE;
	}
	// Add media item to Topic media array.
	topic.m_topicMediaAr.Add(item);

	// Set per param.
	if(setResultsTopic)
		m_resultTopic = topic;

	// Done.
	return TRUE;
}

// Initialise nodesAr param with CNodeItems for the strAr param.
// If strAr Topics do not exist, create them.
BOOL CTopicTree::GetCreateTopicNodes(const CStringArray & nodesList, const MEDIA_TYPE & type,
									 CNodeArray & nodesAr)
{
	// Must have topic list nodes.
	int nNodes = nodesList.GetSize();
	if(!nNodes)
		return FALSE;

	// The topic node we are getting/creating.
	int strArElmt = 0;

	// Find the existing Root Topic array element, if it exists.
	int rootElmt = -1;
	CString nodeStr = nodesList[strArElmt];
	int sz = m_topicTreeAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		if(m_topicTreeAr[i].GetTopicName() == nodeStr)
		{
			rootElmt = i;
			break;
		}
	}
	// If no root topic, build one.
	if(rootElmt == -1)
	{
		// Add empty root topic.
		CTopic topic;
		rootElmt = m_topicTreeAr.Add(topic);

		// Set topic nodes
		CTopic & root = m_topicTreeAr[rootElmt];
		CNodeItem nodeItem;
		nodeItem.m_topicName = nodeStr;
		nodeItem.m_arIndex = rootElmt;
		root.m_nodesAr.Add(nodeItem);
		// Set topic type.
		root.m_topicMediaType = type;
	}
	// Get root topic ref.
	CTopic & rootTopic = m_topicTreeAr[rootElmt];

	// The nodesAr param is this root topics nodes.
	nodesAr = rootTopic.m_nodesAr;

	// If just one node - this TopicTree root.
	if(nNodes == 1)
	{
		return TRUE;
	}
	// If child topics.
	else if(nNodes > 1)
	{
		// Next node.
		strArElmt ++;
		return rootTopic.GetCreateTopicNodes(nodesList, type, strArElmt, nodesAr);
	}
	// No go.
	return FALSE;
}

// Get all favs in TopicTree.
BOOL CTopicTree::GetFavorites(CMediaArray & mediaAr, CNodesArArray & nodesAr, int & nFavsGotten)
{
	// Zero.
	mediaAr.RemoveAll();
	nodesAr.RemoveAll();
	nFavsGotten = 0;

	// Check each topic in tree for favorites.
	int sz = m_topicTreeAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		m_topicTreeAr[i].GetFavorites(mediaAr, nodesAr, nFavsGotten);
	}
	if(nFavsGotten)
		return TRUE;
	return FALSE;
}

// Return a reference to the topic at nodesAr param. 
// Returns errTopic param if a failure,
CTopic& CTopicTree::GetTopicRef(const CNodeArray & nodesAr, CTopic & errTopic)
{
	// new for nodes:
	int nNodes = nodesAr.GetSize();
	if(!nNodes)
	{
		TRACE(_T("CTopicTree GetTopicRef_ node ar empty\n"));
		return errTopic;
	}
	// Get root item at elmnt 0;
	int nodeElmt = 0;
	CNodeItem nodeItem = nodesAr[nodeElmt];

	int sz = m_topicTreeAr.GetSize();

	// Item specifies the array elmt for this array.
	if(nodeItem.m_arIndex + 1 > sz)
	{
		TRACE(_T("CTopicTree GetTopicRef_ node ar elmt out of bounds\n"));
		return errTopic;
	}
	// Get topic ref.
	CTopic & rootTopic = m_topicTreeAr[nodeItem.m_arIndex];

	// This topic must be a parent topic in nodesAr.
	// Note: equals will be set TRUE if all nodes match.
	BOOL equals;
	if(!rootTopic.IsParentNodeOf(nodesAr, equals))
	{
		TRACE(_T("CTopicTree GetTopicRef_ topic not parent in nodesAr.\n"));
		return errTopic;

	}
	// If last node - equals should be TRUE.
	if(nodeElmt + 1 == nNodes) // one node for TopicTree Root.
	{
		if(!equals)
		{
			TRACE(_T("CTopicTree GetTopicRef_ topic nodes do not match nodesAr.\n"));
			return errTopic;
		}
		// Good.
		return rootTopic;
	}
	// Else go to child.
	else if(nodeElmt + 1 < nNodes)
	{
		// Next node.
		nodeElmt++;
		
		// Check bounds.
		int nChildTopics = rootTopic.m_childTopicAr.GetSize();
		nodeItem = nodesAr[nodeElmt];
		if(nodeItem.m_arIndex + 1 > nChildTopics)
		{
			TRACE(_T("CTopicTree GetTopicRef_ child node ar elmt out of bounds\n"));
			return errTopic;
		}
		CTopic & childTopic = rootTopic.m_childTopicAr[nodeItem.m_arIndex];

		// Go to child topic.
		TRACE(_T("CTopicTree GetTopicRef_ going to child topic %s\n"), childTopic.GetTopicName());
		return childTopic.GetTopicRef(nodesAr, nodeElmt, errTopic);
	}
	return errTopic;
}

// Get topic copy from nodesAr param.
BOOL CTopicTree::GetTopic(const CNodeArray & nodesAr, CTopic & topic)
{
	// Get a ref, then set param
	//
	// Get a topic ref.
	CTopic & tempTopic = GetTopicRef(nodesAr, m_errTopic);

	// If fail errTopic was returned.
	if(tempTopic.GetTopicName() == STR_ERROR)
	{
		TRACE(_T("CTopicTree::GetTopic GetTopicRef failed for topic.\n"));
		return FALSE;
	}
	// OK - set param.
	topic = tempTopic;
	return TRUE;
}

// test for ui
// Get topic's child topic names - used for display in ListView.
int CTopicTree::GetTopicChildTopicNamesNodes(CTopic & topic, CStringArray & namesAr, 
											 CNodesArArray & nodesAr)
{
	int nGotten = 0;

	// Zero params.
	namesAr.RemoveAll();
	nodesAr.RemoveAll();

	// Get a topic ref.
	CTopic & topicRef = GetTopicRef(topic.m_nodesAr, m_errTopic);

	// If fail errTopic was returned.
	if(topicRef.GetTopicName() == STR_ERROR)
	{
		TRACE(_T("CTopicTree::GetTopicChildTopicNames GetTopicRef failed for topic.\n"));
		return 0;
	}
	// Fetch immediate child topic names.
	CString str;
	int sz = topicRef.m_childTopicAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		// Topic Name
		str = topicRef.m_childTopicAr[i].GetTopicName();
		namesAr.Add(str);
		// Topic Nodes.
		nodesAr.Add(topicRef.m_childTopicAr[i].m_nodesAr);
		nGotten ++;
	}
	return nGotten;
}


// Print to output
void CTopicTree::PrintContentsMediaItems()
{
	// Load alias array;
	CTopicStrAliasAr alias;
	alias.InitializeTopicAlias();

	// Print each topic's Media Items to output.
	int sz = m_topicTreeAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		m_topicTreeAr[i].PrintTopicMediaItems(alias);
	}
}

// Count number of Topics and Media Items in contents.
void CTopicTree::CountTopicsMediaItems(int & nTopics, int & nMediaItems)
{
	// Zero params.
	nTopics = 0;
	nMediaItems = 0;

	// Count each Topic and its Media Items.
	int sz = m_topicTreeAr.GetSize();
	nTopics += sz;
	for(int i = 0; i < sz; i++)
	{
		m_topicTreeAr[i].CountTopicsMediaItems(nTopics, nMediaItems);
	}
}

// replaced by WriteMediaRecordFormatEx - delete
// Write media items to txt file per DsLav Media Record Format.
BOOL CTopicTree::WriteTopicTreeMediaItemsxxx(CStdioFile & file, BOOL & bExcludeDiscFiles, 
										  int & nWritten)
{
	// Load alias array;
	CTopicStrAliasAr alias;
	alias.InitializeTopicAlias();

	// Print each topic's Media Items to output.
	int sz = m_topicTreeAr.GetSize();
	if(!sz)
		return FALSE;

	// Write header.
	CString str;
	str = _T('\n');
	file.WriteString(str);
	str = _T("// DsLav Media List\n");
	file.WriteString(str);
	str = _T("// Media Record Format 0.1\n");
	file.WriteString(str);
	str = _T("// See MediaRecordFormat.txt for definitions.\n");
	file.WriteString(str);
	str = _T('\n');
	file.WriteString(str);

	BOOL ret = TRUE;
	for(int i = 0; i < sz; i++)
	{
	//	if(!m_topicTreeAr[i].WriteTopicMediaItemsxxx(file, alias, bExcludeDiscFiles, nWritten))
	//		ret = FALSE;
	}
	return ret;
}

// Write media items to txt file per DsLav Media Record Format.
BOOL CTopicTree::WriteMediaRecordFormatEx(CStdioFileEx & file, CTopicStrAliasAr & alias,
										  BOOL writeAlias, BOOL & excludeDisc, 
										  int & nWritten)
{
	// Note CStdioFileEx does not write the carriage return–linefeed pairs
	// for '\n' - we must do it ourselves.
	CString newline = _T("\r\n");

	// Print each topic's Media Items to output.
	int sz = m_topicTreeAr.GetSize();
	if(!sz)
		return FALSE;

	// Write header.
	CString str;
	file.WriteString(newline);
	file.WriteString(_T("// DsLav Media List") + newline);
	file.WriteString(newline);
	file.WriteString(_T("// Media Record Format 0.1"));
	file.WriteString(newline);
	file.WriteString(_T("// See MediaRecordFormat.txt for definitions."));
	file.WriteString(newline);

	BOOL ret = TRUE;
	for(int i = 0; i < sz; i++)
	{
		if(!m_topicTreeAr[i].WriteMediaRecordFormatEx(file, alias, excludeDisc, 
													  writeAlias, nWritten))
			ret = FALSE;
	}
	return ret;
}

// Write media items to M3u Format. Disc files not written
BOOL CTopicTree::WriteM3uStreamsEx(CStdioFileEx & file, int & nWritten)
{
	// Note CStdioFileEx does not write the carriage return–linefeed pairs
	// for '\n' - we must do it ourselves.
	CString newline = _T("\r\n");

	// Print each topic's Media Items to output.
	int sz = m_topicTreeAr.GetSize();
	if(!sz)
		return FALSE;

	// Write header.
	CString str;
	// Add Blank line.
	file.WriteString(newline);
	file.WriteString(_T("#EXTM3U"));
	file.WriteString(newline);
	// Add Blank line.
	file.WriteString(newline);

	// Write each ROOT topic.
	BOOL ret = TRUE;
	for(int i = 0; i < sz; i++)
	{
		if(!m_topicTreeAr[i].WriteM3uStreamsEx(file, nWritten))
			ret = FALSE;
	}
	return ret;
}

// Move a Media Item src to NodesAr dest topic.
// Note the nodesArDest parent topic must exist.
// Called from: CDsLavDoc::MoveMediaItem.
BOOL CTopicTree::MoveMediaItem(const CMediaItem & mediaItem, 
							   const CNodeArray & nodesArSrc,
							   const CNodeArray & nodesArDest )
{
	// Alteratively, we could delete src and add dest
	// with AddMediaItemToTree().

	// Get the media item's topic.
	CTopic & topicSrc = GetTopicRef(nodesArSrc, m_errTopic);

	// If fail errTopic was returned.
	if(topicSrc.GetTopicName() == STR_ERROR)
	{
		TRACE(_T("CTopicTree::MoveMediaItem GetTopicRef failed for topicSrc.\n"));
		return FALSE;
	}

	// Get the dest topic.
	CTopic & topicDest = GetTopicRef(nodesArDest, m_errTopic);

	// If fail errTopic was returned.
	if(topicDest.GetTopicName() == STR_ERROR)
	{
		TRACE(_T("CTopicTree::MoveMediaItem GetTopicRef failed for topicDest.\n"));
		return FALSE;
	}

	// Add mediaItemSrc to topicDest.
	//
	// Copy media item.
	CMediaItem mediaItemDest = mediaItem;
	// Set the media items nodes to the dest topic nodes.
	nodesArDest.GetTopicNameStrings(mediaItemDest.m_nodesList);
	// Add media item to dest topic.
	topicDest.m_topicMediaAr.Add(mediaItemDest);

	// Delete mediaItemSrc from topicSrc.
	//
	if(!topicSrc.DeleteMediaItem(mediaItem))
	{
		TRACE(_T("CTopicTree::MoveMediaItem could not delete media item.\n"));
		return FALSE;
	}
	// OK.
	return TRUE;
}

// Move multiple Media Items in NodesAr src to NodesAr dest topics.
// Note the nodesArDest parent topic must exist.
// Called from: CDsLavDoc::MoveMediaItemMulti.
BOOL CTopicTree::MoveMediaItemMulti(const CMediaArray & mediaAr, 
									const CNodeArray & nodesArSrc,
									const CNodeArray & nodesArDest,
									BOOL setResultsTopic)
{
	// Get the media item's topic.
	CTopic & topicSrc = GetTopicRef(nodesArSrc, m_errTopic);

	// If fail errTopic was returned.
	if(topicSrc.GetTopicName() == STR_ERROR)
	{
		TRACE(_T("CTopicTree::MoveMediaItem GetTopicRef failed for topicSrc.\n"));
		return FALSE;
	}

	// Get the dest topic.
	CTopic & topicDest = GetTopicRef(nodesArDest, m_errTopic);

	// If fail errTopic was returned.
	if(topicDest.GetTopicName() == STR_ERROR)
	{
		TRACE(_T("CTopicTree::MoveMediaItem GetTopicRef failed for topicDest.\n"));
		return FALSE;
	}

	// Add mediaItem in mediaAr to topicDest.
	//
	CMediaItem mediaItem;
	int sz = mediaAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		// Copy media item.
		mediaItem = mediaAr[i];
		// Set the media items nodes to the dest topic nodes.
		nodesArDest.GetTopicNameStrings(mediaItem.m_nodesList);
		// Add media item to dest topic.
		topicDest.m_topicMediaAr.Add(mediaItem);
	}

	// Delete mediaItemSrc from topicSrc.
	//
	for(int i = 0; i < sz; i++)
	{
		// Copy media item.
		mediaItem = mediaAr[i];
		// Delete one at a time.
		if(!topicSrc.DeleteMediaItem(mediaItem))
		{
			TRACE(_T("CTopicTree::MoveMediaItemMulti could not delete media item.\n"));
			return FALSE;
		}
	}
	// OK.
	if(setResultsTopic)
		m_resultTopic = topicDest;
	return TRUE;
}

// Replace revised Media Item Old with Media Item New.
// Init topicResult with final mediaItemNew location.
// Note the mediaItemNew parent topic need not exist.
// Called from: CDsLavDoc::OnEditMediaItem().
BOOL CTopicTree::ReplaceMediaItem(const CNodeArray & nodesArOld, CMediaItem & mediaItemOld, 
								  CMediaItem & mediaItemNew, BOOL setResultsTopic)
{
	// The New MediaItem is the Old MediaItem with its nodes revised.
	// Since it's unknown if the New MediaItem dest topic exists, we 
	// will let AddMediaItemToTree() build new topics as needed.

	// Get the Old media item's topic.
	CTopic & topicOld = GetTopicRef(nodesArOld, m_errTopic);

	// If fail errTopic was returned.
	if(topicOld.GetTopicName() == STR_ERROR)
	{
		TRACE(_T("CTopicTree::ReplaceMediaItem GetTopicRef failed for topicOld.\n"));
		return FALSE;
	}
	// Delete mediaItemOld from topicSrc.
	if(!topicOld.DeleteMediaItem(mediaItemOld))
	{
		TRACE(_T("CTopicTree::ReplaceMediaItem could not delete MediaItem Old.\n"));
		return FALSE;
	}
	// Add MediaItem New to the TopicTree.
	// Overload initializes topicResult with the final 
	// topic location of the mediaItemNew MediaItem.
	if(!AddMediaItemToTree(mediaItemNew, setResultsTopic))
	{
		TRACE(_T("CTopicTree::ReplaceMediaItem AddMediaItemToTree failed for MediaItem New.\n"));
		return FALSE;
	}

	// OK.
	return TRUE;
}

// Update mediaItemOld with mediaItemRevised.
// Note the mediaItemUpdate is a revised mediaItemOld
// with matching topic nodes.
// Called from: CDsLavDoc::OnEditMediaItem().
BOOL CTopicTree::UpdateMediaItem(const CNodeArray & nodesAr, CMediaItem & mediaItemOld, 
								 CMediaItem & mediaItemUpdate, BOOL setResultsTopic)
{
	// Get the old/updated media item's topic.
	CTopic & topic = GetTopicRef(nodesAr, m_errTopic);

	// If fail errTopic was returned.
	if(topic.GetTopicName() == STR_ERROR)
	{
		TRACE(_T("CTopicTree::UpdateMediaItem GetTopicRef failed for topic.\n"));
		return FALSE;
	}
	// Replace old MediaItem with updated MediaItem.
	//
	int index = -1;
	int sz = topic.m_topicMediaAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		if(topic.m_topicMediaAr[i] == mediaItemOld)
		{
			index = i;
			break;
		}
	}
	// If found, replace.
	if(index != -1)
	{
		topic.m_topicMediaAr[index] = mediaItemUpdate;
		if(setResultsTopic)
			m_resultTopic = topic;
		return TRUE;
	}
	// Error.
	TRACE(_T("CTopicTree::UpdateMediaItem - old MediaItem not found.\n"));
	return FALSE;
}

// Sort nodesAr topic's media array.
// Called from: CDsLavDoc::ListViewSortMediaItems.
BOOL CTopicTree::SortMediaArray(const CNodeArray & nodesAr, BOOL ascend, BOOL setResultsTopic)
{
	// Get the media array's topic.
	CTopic & topic = GetTopicRef(nodesAr, m_errTopic);

	// If fail errTopic was returned.
	if(topic.GetTopicName() == STR_ERROR)
	{
		TRACE(_T("CTopicTree::SortMediaArray GetTopicRef failed for topic.\n"));
		return FALSE;
	}
	// Sort.
	topic.m_topicMediaAr.SortMediaName(ascend);

	if(setResultsTopic)
		m_resultTopic = topic;
	// OK.
	return TRUE;
}

// 9/22-1 rework for move above bug
// Move a topic among its sibling topics within a single array.
// Called from CDsLavDoc::MoveTopicSibling().
BOOL CTopicTree::MoveTopicSibling(const CNodeArray & nodesArSrc, const CNodeArray & nodesArDest,
								  BOOL setAfter, BOOL setResultsTopic)
{
	TRACE(_T("Entering CTopicTree::MoveTopicSibling.\n"));

	// Ensure nodes are in the same child array.
	//
	// Size.
	int szSrc = nodesArSrc.GetSize();
	int szDest = nodesArDest.GetSize();
	if(szSrc != szDest)
		return FALSE;

	// Ensure siblings.
	BOOL bAreRoots = FALSE;
	if(!nodesArSrc.IsSiblingNodeOf(nodesArDest, bAreRoots))
	{
		TRACE(_T("CTopicTree::MoveTopicSibling not siblings\n"));
		return FALSE;
	}
	// Get a copy of topic being moved.
	CTopic topicSrc;
	if(!GetTopic(nodesArSrc, topicSrc)) 
		return FALSE;

	// Get the topic node for src & dest.
	CNodeItem topicNodeSrc, topicNodeDest;
	nodesArSrc.GetTopicNodeItem(topicNodeSrc);
	nodesArDest.GetTopicNodeItem(topicNodeDest);

	// If only one node, a ROOT topic.
	if(szSrc == 1)
	{
		int iDelete = topicNodeSrc.m_arIndex;
		int iInsert = topicNodeDest.m_arIndex;

		// Adjust index for flag param.
		//  Note no adjustment for setBefore as that is the iInsert location
		if(setAfter)
			iInsert++;

		// Ensure.
		if(iInsert < 0)
			iInsert = 0;

		// Check if inserting at Favorites - a special case.
		// Note Favorites is not part of the TopicTree.
		if(topicNodeDest.m_topicName == STR_FAVS && setAfter)
			iInsert = 0;

		// If inserting at end of array.
		BOOL bAdd = FALSE;
		int sz = m_topicTreeAr.GetSize();
		if(iInsert >= sz)
		{
			bAdd = TRUE;
			iInsert = sz -1;
		}
		// Adjust delete index.
		if(iInsert < iDelete)
			iDelete ++;

		// Insert the copy.
		if(bAdd)										// add to end
			m_topicTreeAr.Add(topicSrc);
		else											// insert
			m_topicTreeAr.InsertAt(iInsert, topicSrc);

		// Delete original.
		m_topicTreeAr.RemoveAt(iDelete);

		// Get new sz.
		sz = m_topicTreeAr.GetSize();

		// Topic indices must be adjusted from first modification down.
		int adjustFrom = iInsert < iDelete ? iInsert : iDelete;
		int nResets = 0;
		for(int i = adjustFrom; i < sz; i ++)
		{
			CTopic & topic = m_topicTreeAr[i];
			// Reset topic ar index.
			topic.m_nodesAr.UpdateTopicIndex(i);
			// Reset using topics new ar index.
			topic.ResetChildTopicAndMediaNodes(nResets);
		}
		// If success.
		if(iInsert < sz)
		{
			// Get a copy of topic src at its new location.
			CTopic newTopic = m_topicTreeAr[iInsert];
			// Save per param.
			if(setResultsTopic)
				m_resultTopic = newTopic;

			TRACE(_T("CTopicTree::MoveTopicSibling Root %s success %d node indices reset.\n"), 
							newTopic.GetTopicName(), nResets);
			return TRUE;
		}
		// Problem.
		TRACE(_T("CTopicTree::MoveTopicSibling failed for root topic.\n"));
		return FALSE;
	}
	// Else a child topic array - much the same as above.
	else
	{
		// Get ref to the parent topic - the parent of sibling to move.
		CNodeArray nodesArParent;
		nodesArSrc.GetParentNodes(nodesArParent);
		CTopic & parentTopic = GetTopicRef(nodesArParent, m_errTopic);

		// If fail errTopic was returned.
		if(parentTopic.GetTopicName() == STR_ERROR)
		{
			TRACE(_T("CTopicTree::MoveTopicSibling  GetTopicRef failed.\n"));
			return FALSE;
		}
		// Get current sz.
		int sz = parentTopic.m_childTopicAr.GetSize();

		// Delete first.
		int iDelete = topicNodeSrc.m_arIndex;
		parentTopic.m_childTopicAr.RemoveAt(iDelete);

		// Get sz post deletion.
		sz = parentTopic.m_childTopicAr.GetSize();

		// Insert at new location.
		//
		int iInsert = topicNodeDest.m_arIndex;

		// Index is less one if deleted before.
		if(iInsert > iDelete)
			iInsert --;

		// Adjust index for flag param.
		//  Note no adjustment for setBefore as that is the iInsert location
		if(setAfter)
			iInsert ++;

		// Ensure. 
		if(iInsert < 0) // < 0 is normal if inserting at start.
			iInsert = 0;

		// Insert.
		if(iInsert > sz)
			iInsert = parentTopic.m_childTopicAr.Add(topicSrc);
		else
			parentTopic.m_childTopicAr.InsertAt(iInsert, topicSrc);

		// Get sz post insertion.
		sz = parentTopic.m_childTopicAr.GetSize();

		// Topic indices must be adjusted from first modification down.
		int adjustFrom = iInsert < iDelete ? iInsert : iDelete;
		int nResets = 0;
		for(int i = adjustFrom; i < sz; i ++)
		{
			CTopic & topic = parentTopic.m_childTopicAr[i];
			// Reset topic ar index.
			topic.m_nodesAr.UpdateTopicIndex(i);
			// Reset using topics new ar index.
			topic.ResetChildTopicAndMediaNodes(nResets);
		}

		// Get a copy of topic src at its new location.
		if(iInsert < sz)
		{
			CTopic newTopic = parentTopic.m_childTopicAr[iInsert];
			m_resultTopic = newTopic;
			TRACE(_T("CTopicTree::MoveTopicSibling Child %s success %d node indices reset.\n"), 
							newTopic.GetTopicName(), nResets);
			return TRUE;
		}
		// Problem.
		TRACE(_T("CTopicTree::MoveTopicSibling failed for child topic.\n"));
		return FALSE;
	}
	// No go.
	return FALSE;
}

// 9/22-1 rework - allow any topic to move anywhere in Tree.
// Move a Topic from src to dest.
//  Called from: CDsLavDoc::MoveTopic2().
BOOL CTopicTree::MoveTopic(const CNodeArray & nodesArSrc, 
						   const CNodeArray & nodesArDest,
						   MOVE_TYPE moveType, BOOL setResultsTopic)
{
	/*dbg
	CNodeItem item; int sz2, i2;
	sz2 = nodesArSrc.GetSize();
	for(i2 = 0; i2 < sz2; i2++)
		item = nodesArSrc[i2];
	sz2 = nodesArDest.GetSize();
	for(i2 = 0; i2 < sz2; i2++)
		item = nodesArDest[i2];*/

	// Ensure.
	if(moveType == MOVE_NONE)
	{
		TRACE(_T("CTopicTree::MoveTopic moveType = NONE.\n"));
		return FALSE;
	}
	CTopic topicSrc, topicDest;

	// Get the Src CTopic.
	if(!GetTopic(nodesArSrc, topicSrc))
	{
		TRACE(_T("CTopicTree::MoveTopic GetTopic failed for src\n"));
		return FALSE;
	}
	// Get Dest CTopic.
	if(!GetTopic(nodesArDest, topicDest))
	{
		TRACE(_T("CTopicTree::MoveTopic GetTopic failed for dest\n"));
		return FALSE;
	}
	// Modify the TopicTree from the bottom up.
	//  This is needed as topic nodes and indices are 
	//  reset for all topics below a topic that has 
	//  been moved or deleted.
	BOOL bDeleteSrcFirst = -1;
	int szSrc = nodesArSrc.GetSize();
	int szDest = nodesArDest.GetSize();
	int sz = szSrc > szDest ? szSrc : szDest;
	int iSrc = -1, iDest = -1;
	for(int i = 0; i < sz; i ++)
	{
		iSrc = i < szSrc ? nodesArSrc[i].m_arIndex : -1;
		iDest = i < szDest ? nodesArDest[i].m_arIndex : -1;

		// User dragged scr to a higher topic.
		if(iSrc > iDest)
		{
			bDeleteSrcFirst = TRUE;
			break;
		}
		// User dragged src to a lower topic.
		if(iSrc < iDest)
		{
			bDeleteSrcFirst = FALSE;
			break;
		}
	}
	// Must have a result.
	if(bDeleteSrcFirst == -1)
	{
		TRACE(_T("CTopicTree::MoveTopic deleteSrcFirst = -1\n"));
		return FALSE;
	}
	// Are src and dest siblings.
	BOOL bAreSiblings = FALSE, bAreRoots;
	if(nodesArDest.IsSiblingNodeOf(nodesArSrc, bAreRoots))
		bAreSiblings = TRUE;

	// Are src or dest ROOTs.
	BOOL bSrcRoot = nodesArSrc.GetSize() == 1 ? TRUE : FALSE;
	BOOL bDestRoot = nodesArDest.GetSize() == 1 ? TRUE : FALSE;

	// If moving src topic to a location above or below dest topic.
	if(moveType == MOVE_ABOVE || moveType == MOVE_BELOW)
	{
		BOOL bSetAfter = moveType == MOVE_BELOW ? TRUE : FALSE;

		// If moving within a single topic array of siblings.
		if(bAreSiblings)
		{
			// Move - sets m_resultTopic per setResultsTopic.
			if(!MoveTopicSibling(nodesArSrc, nodesArDest, bSetAfter, setResultsTopic))
			{
				TRACE(_T("CTopicTree::MoveTopic MoveTopicSibling failed\n"));
				return FALSE;
			}
			return TRUE;
		}
		// Else moving among topic arrays.
		else
		{
			//dbg
			CString strSrc = topicSrc.GetTopicName();
			CString strDest = topicDest.GetTopicName();

			// If deleting src topic before moving to destination.
			if(bDeleteSrcFirst)
			{
				// Delete the src Topic.
				if(!DeleteTopic(nodesArSrc, FALSE))
				{
					TRACE(_T("CTopicTree::MoveTopic moving DeleteTopic first failed.\n"));
					return FALSE;
				}
			}
			// Destination topic not a root.
			if(!bDestRoot)
			{
				// Get dest parent nodes array.
				CNodeArray nodesArParent;
				nodesArDest.GetParentNodes(nodesArParent);

				// Get dest parent topic ref.
				CTopic & topicParent = GetTopicRef(nodesArParent, m_errTopic);
				// If fail errTopic was returned.
				if(topicParent.GetTopicName() == STR_ERROR)
				{
					TRACE(_T("CTopicTree::MoveTopic  GetTopicRef child failed.\n"));
					return FALSE;
				}
				//dbg
				CString strParent = topicParent.GetTopicName();

				// Get insertion index in parent array.
				int sz = nodesArDest.GetSize();
				CNodeItem nodeItem = nodesArDest[sz - 1];			
				int iInsert = nodeItem.m_arIndex;

				// Adjust for position.
				//  Note no adjustment for setBefore as that is the iInsert location.
				if(moveType == MOVE_BELOW)
					iInsert ++;

				// Ensure.
				if(iInsert < 0)
				{
					TRACE(_T("CTopicTree::MoveTopic child iInsert < 1.\n"));
					return FALSE;
				}
				// Get Parent ar sz.
				sz = topicParent.m_childTopicAr.GetSize();

				// If placing at end of Parent array.
				BOOL bAdd = iInsert >= sz ? TRUE : FALSE;

				// Copy src topic.
				CTopic topic = topicSrc;
				// Set nodes of src topic copy to parents nodes.
				topic.m_nodesAr = topicParent.m_nodesAr;
				// Add scr topic node item to src topic copy.
				CNodeItem srcNodeItem;
				topicSrc.m_nodesAr.GetTopicNodeItem(srcNodeItem);
				topic.m_nodesAr.Add(srcNodeItem);

				// Insert or add scr topic copy into Parent ar.
				if(bAdd)
					topicParent.m_childTopicAr.Add(topic);
				else
					topicParent.m_childTopicAr.InsertAt(iInsert, topic);

				// Get new sz.
				sz = topicParent.m_childTopicAr.GetSize();

				// Update parent topic child ar nodes from insert location.
				int nResets = 0;
				for(int i = iInsert; i < sz; i ++)
				{
					CTopic & topic = topicParent.m_childTopicAr[i];
					// Reset topic ar index.
					topic.m_nodesAr.UpdateTopicIndex(i);
					// Reset using topics new ar index.
					topic.ResetChildTopicAndMediaNodes(nResets);
				}
				// Set per param.
				if(setResultsTopic)
					m_resultTopic = topic;
			}
			// Destination topic is a Root.
			else if(bDestRoot)
			{
				// Get Root Topic ref.
				CTopic & topicRoot = GetTopicRef(nodesArDest, m_errTopic);
				if(topicRoot.GetTopicName() == STR_ERROR)
				{
					TRACE(_T("CTopicTree::MoveTopic  GetTopicRef root failed.\n"));
					return FALSE;
				}
				// Get Root Topic insertion index in m_topicTreeAr array.
				int sz = nodesArDest.GetSize();
				CNodeItem nodeItem = nodesArDest[sz -1];			
				int iInsert = nodeItem.m_arIndex;

				// Adjust insert element for position.
				//  Note no adjustment for setBefore as that is the iInsert location.
				if(moveType == MOVE_BELOW)
					iInsert ++;

				// Ensure.
				if(iInsert < 0)
				{
					TRACE(_T("CTopicTree::MoveTopic root iInsert < 1.\n"));
					return FALSE;
				}
				// Get dest m_topicTreeAr sz.
				sz = m_topicTreeAr.GetSize();

				// If placing at end of Parent array.
				BOOL bAdd = iInsert >= sz ? TRUE : FALSE;

				// Get last node in src topic nodes array.
				CNodeItem srcNodeItem;
				topicSrc.m_nodesAr.GetTopicNodeItem(srcNodeItem);

				// Set node item index to insert element.
				srcNodeItem.m_arIndex = iInsert;

				// Copy src topic.
				CTopic newRootTopic = topicSrc;

				// Set newRootTopic's nodes array to srcNodeItem
				// for a new Root Topic.
				newRootTopic.m_nodesAr.RemoveAll();
				newRootTopic.m_nodesAr.Add(srcNodeItem);

				// Insert or Add newRootTopic into m_topicTreeAr.
				if(bAdd)
					m_topicTreeAr.Add(newRootTopic);
				else
					m_topicTreeAr.InsertAt(iInsert, newRootTopic);

				// Root Topic indices must be adjusted from iInsert down.
				int nResets = 0;
				for(int i = iInsert; i < sz; i ++)
				{
					CTopic & topic = m_topicTreeAr[i];
					// Reset topic ar index.
					topic.m_nodesAr.UpdateTopicIndex(i);
					// Reset using topics new ar index.
					topic.ResetChildTopicAndMediaNodes(nResets);
				}
				// Set per param.
				if(setResultsTopic)
					m_resultTopic = newRootTopic;
			}
			// If deleting src topic after moving to destination.
			if(!bDeleteSrcFirst)
			{
				// Delete the src Topic.
				if(!DeleteTopic(nodesArSrc, FALSE))
				{
					TRACE(_T("CTopicTree::MoveTopic moving DeleteTopic last failed.\n"));
					return FALSE;
				}
			}
			// Done.
			return TRUE;			
		}
	}
	// Else if inserting src topic into another topic - 
	// src becomes a child of dest.
	else if(moveType == MOVE_ON)
	{
		// User dragged scr to a higher topic.
		if(bDeleteSrcFirst)
		{
			if(!DeleteTopic(nodesArSrc))
			{
				TRACE(_T("CTopicTree::MoveTopic DeleteTopic failed.\n"));
				return FALSE;
			}
			if(!InsertTopic(nodesArDest, topicSrc, setResultsTopic))
			{
				TRACE(_T("CTopicTree::MoveTopic InsertTopic failed.\n"));
				return FALSE;
			}
			TRACE(_T("CTopicTree::MoveTopic - topic deleted then inserted\n"));
		}
		// User dragged src to a lower topic.
		else
		{
			if(!InsertTopic(nodesArDest, topicSrc, setResultsTopic))
			{
				TRACE(_T("CTopicTree::MoveTopic InsertTopic2 failed.\n"));
				return FALSE;
			}
			if(!DeleteTopic(nodesArSrc))
			{
				TRACE(_T("CTopicTree::MoveTopic DeleteTopic2 failed.\n"));
				return FALSE;
			}
			TRACE(_T("CTopicTree::MoveTopic - topic inserted then deleted\n"));
		}
		return TRUE;
	}
	// Should not get here.
	return FALSE;
}

// Delete the topic at nodesArray param.
BOOL CTopicTree::DeleteTopic(const CNodeArray & nodesArray, BOOL setResultsTopic)
{
	int nNodes = nodesArray.GetSize();

	//dbg
	CString str;
	for(int ii = 0; ii < nNodes; ii++)
		str = nodesArray[ii].m_topicName;

	// If only one node, there are no parents - it is 
	// a Root node to be deleted.
	if(nNodes == 1)
	{
		int iDelete = nodesArray[0].m_arIndex;
		int nRoots = m_topicTreeAr.GetSize();
		if(iDelete + 1 > nRoots)
		{
			TRACE(_T("ERROR CTopicTree::DeleteTopic - item index out of bounds for m_topicTreeAr.\n"));
			return FALSE;
		}
		// Verify.
		CString rootName = m_topicTreeAr[iDelete].GetTopicName();
		CString nodeName = nodesArray[0].m_topicName;
		if(rootName != nodeName)
		{
			TRACE(_T("ERROR CTopicTree::DeleteTopic - rootName does not match nodeName.\n"));
			return FALSE;
		}
		// Delete the root.
		m_topicTreeAr.RemoveAt(iDelete);

		// Reset the Root topic indices from iDelete down.
		// Note iDelete is now the Root after moved up.
		int nResets = 0;
		nRoots = m_topicTreeAr.GetSize();
		for(int i = iDelete; i < nRoots; i ++)
		{
			// Set index for this Root - just one node item.
			m_topicTreeAr[i].m_nodesAr.UpdateTopicIndex(i);

			// Update Root's child topic nodes.
			m_topicTreeAr[i].ResetChildTopicAndMediaNodes(nResets);
		}
		// dbg
		TRACE(_T("CTopicTree::DeleteTopic %d m_topicTreeAr indices reset.\n"), nResets);

		// If setting m_resultTopic, use the previous Root topic.
		if(setResultsTopic)
		{
			int iPrev = -1;
			if(iDelete == 0)
				iPrev = 0;
			else if(iDelete - 1 < nRoots)
				iPrev = iDelete - 1;
			if(iPrev == -1)
			{
				return FALSE;
			}
			m_resultTopic = m_topicTreeAr[iPrev];
		}
		return TRUE;
	}

	// Else get the Topic Node we are deleting - the last node in nodesArray param. 
	CNodeItem nodeToDelete;
	if(!nodesArray.GetTopicNodeItem(nodeToDelete))
	{
		TRACE(_T("ERROR CTopicTree::DeleteTopic GetTopicNodeItem failed.\n"));
		return FALSE;
	}
	// Get parent nodes - all nodes but the last.
	CNodeArray parentNodes;
	if(!nodesArray.GetParentNodes(parentNodes))
	{
		TRACE(_T("ERROR CTopicTree::DeleteTopic GetParentNodes failed.\n"));
		return FALSE;
	}
	// Ensure a node remains - just 1 if parent is a root.
	int nParentNodes = parentNodes.GetSize();
	if(!nParentNodes)
	{
		TRACE(_T("CTopicTree::DeleteTopic GetTopicRef no parent nodes.\n"));
		return FALSE;
	}	

	// Get a ref to the Parent topic.
	CTopic & parentTopic = GetTopicRef(parentNodes, m_errTopic);

	// If fail errTopic was returned.
	if(parentTopic.GetTopicName() == STR_ERROR)
	{
		TRACE(_T("ERROR CTopicTree::DeleteTopic GetTopicRef failed for parentTopic.\n"));
		return FALSE;
	}
	// Get child count.
	int nChildTopics = parentTopic.m_childTopicAr.GetSize();

	// Get index of the node to delete.
	int iDelete = nodeToDelete.m_arIndex;

	// Check bounds.
	if(iDelete + 1 > nChildTopics)
	{
		TRACE(_T("ERROR CTopicTree::DeleteTopic - item index out of bounds for parent topic child array.\n"));
		return FALSE;
	}
	// Verify.
	CString topicName = parentTopic.m_childTopicAr[iDelete].GetTopicName();
	CString nodeName = nodeToDelete.m_topicName;
	if(topicName != nodeName)
	{
		TRACE(_T("ERROR CTopicTree::DeleteTopic - topicName does not match nodeName.\n"));
		return FALSE;
	}

	// Delete it.
	parentTopic.m_childTopicAr.RemoveAt(iDelete);

	// Get new sz.
	nChildTopics = parentTopic.m_childTopicAr.GetSize();

	// Topic node indices must be adjusted from iDelete down.
	int nResets = 0;
	for(int i = iDelete; i < nChildTopics; i ++)
	{
		CTopic & topic = parentTopic.m_childTopicAr[i];
		// Reset topic ar index.
		topic.m_nodesAr.UpdateTopicIndex(i);
		// Reset using topics new ar index.
		topic.ResetChildTopicAndMediaNodes(nResets);
	}
	// dbg
	TRACE(_T("CTopicTree::DeleteTopic %d parentTopic child indices reset.\n"), nResets);

	/*dbg
	for(i = 0; i < parentTopic.m_nodesAr.GetSize(); i++)
		str = parentTopic.m_nodesAr[i].m_topicName;*/

	// If setting m_resultTopic, set the parent topic.
	if(setResultsTopic)
		m_resultTopic = parentTopic;

	return TRUE;
}

// Insert/Add a topic to an existing topic located at nodesArDest.
BOOL CTopicTree::InsertTopic(const CNodeArray & nodesArDest, CTopic & newTopic,
							 BOOL setResultsTopic)
{
	// Must have dest nodes.
	int nNodes = nodesArDest.GetSize();
	if(!nNodes)
	{
		TRACE(_T("ERROR CTopicTree::InsertTopic no nodes.\n"));
		return FALSE;
	}
	// Get topic param topic node - must have.
	CNodeItem newNode;
	if(!newTopic.m_nodesAr.GetTopicNodeItem(newNode))
	{
		TRACE(_T("ERROR CTopicTree::InsertTopic GetTopicNodeItem failed.\n"));
		return FALSE;
	}
	// Get a topic ref to parent of newTopic - can be
	// a TopicTree root.
	CTopic & parentTopic = GetTopicRef(nodesArDest, m_errTopic);

	// If fail errTopic was returned.
	if(parentTopic.GetTopicName() == STR_ERROR)
	{
		TRACE(_T("ERROR CTopicTree::InsertTopic GetTopicRef failed for nodesArDest topic.\n"));
		return FALSE;
	}

	// Add the newTopic to parent topic's child ar.
	int index = parentTopic.m_childTopicAr.Add(newTopic);

	// Get a ref to the added topic.
	CTopic & childTopic = parentTopic.m_childTopicAr[index];

	// The new childTopic nodes are the parent's nodes 
	// plus the newTopic's topic node saved above.
	childTopic.m_nodesAr = parentTopic.m_nodesAr;
	newNode.m_arIndex = index;
	childTopic.m_nodesAr.Add(newNode);

	// Update all new childTopic's child topic nodes  
	// and MediaItem topic lists - should not fail.
	int nResets = 0;
	if(!childTopic.ResetChildTopicAndMediaNodes(nResets))
	{
		TRACE(_T("ERROR CTopicTree::InsertTopic ResetChildTopicNodes failed.\n"));
		return FALSE;
	}
	// Done.
	if(setResultsTopic)
		m_resultTopic = childTopic;
	return TRUE;
}

// Create/Add topic and children per nodesList, with MediaType per type.
BOOL CTopicTree::InsertTopic(const CStringArray & nodesList, const MEDIA_TYPE & type, 
							 BOOL setResultsTopic)
{
	// Must have a type.
	if(type == NONE)
		return FALSE;

	// Get number of topic nodes.
	int nNodes = nodesList.GetSize();

	// If no topic nodes, use default per type.
	if(!nNodes)
		return FALSE;

	// Create topic and children per nodesList, with MediaType per type,
	// and init nodeAr with topic topic names and TopicTree indices.
	CNodeArray nodeAr;
	if(!GetCreateTopicNodes(nodesList, type, nodeAr))
	{
		TRACE(_T("CTopicTree::InsertTopic GetCreateTopicNodes failed.\n"));
		return FALSE;
	}
	// Get topic ref - note m_errTopic initialized in Zero().
	CTopic & topic = GetTopicRef(nodeAr, m_errTopic);

	// If fail errTopic was returned.
	if(topic.GetTopicName() == STR_ERROR)
	{
		TRACE(_T("CTopicTree::InsertTopic GetTopicRef failed.\n"));
		return FALSE;
	}
	// Set per param.
	if(setResultsTopic)
		m_resultTopic = topic;

	// Done.
	return TRUE;
}

// Update a topic's Name and or Type.
BOOL CTopicTree::UpdateTopic(const CNodeArray & nodesAr, CString topicName, 
							 MEDIA_TYPE	type, BOOL setResultsTopic)
{
	// Get a ref to the topic at nodesAr.
	CTopic & topic = GetTopicRef(nodesAr, m_errTopic);
	// If fail errTopic was returned.
	if(topic.GetTopicName() == STR_ERROR)
	{
		TRACE(_T("ERROR CTopicTree::UpdateTopic GetTopicRef failed.\n"));
		return FALSE;
	}

	// Update topicName and child topic node names.
	if(topic.GetTopicName() != topicName)
	{
		topic.SetTopicName(topicName);
		int nResets = 0;
		topic.ResetChildTopicAndMediaNodes(nResets);
		int g = 0; //18 resets
	}
	// Update Type - this topic only as user decides.
	if(topic.m_topicMediaType != type)
	{
		topic.m_topicMediaType = type;
	}
	// Set per flag.
	if(setResultsTopic)
		m_resultTopic = topic;
	return TRUE;
}

// Called from:
//	CDsLavTreeView::ListViewDropMediaItem()
//	CDsLavDoc::SetMediaItemFavorites()
// Set favorites flag for mediaItem located at nodesAr.
BOOL CTopicTree::SetMediaItemFavorites(const CNodeArray & nodesAr, const CMediaItem & mediaItem, 
									   BOOL isFavorite, BOOL setResultsTopic)
{
	// Get topic ref.
	CTopic & topic = GetTopicRef(nodesAr, m_errTopic);

	CString name = topic.GetTopicName();

	// If fail errTopic was returned.
	if(name == STR_ERROR)
	{
		TRACE(_T("CTopicTree::SetMediaItemFavorites failed.\n"));
		return FALSE;
	}
	// Check for media item.
	int sz = topic.m_topicMediaAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		CMediaItem & item = topic.m_topicMediaAr[i];

		// If a match set per param.
		if(item == mediaItem)
		{
			item.m_isFavorite = isFavorite;
			if(setResultsTopic)
				m_resultTopic = topic;
			return TRUE;
		}
	}
	return FALSE;
}

// Called from:
//	CDsLavDoc::DeleteMediaItem()
// Delete media item in nodesAr topic.
BOOL CTopicTree::DeleteMediaItem(const CNodeArray & nodesAr, const CMediaItem & mediaItem,
								 BOOL setResultsTopic)
{
	// Get topic ref.
	CTopic & topic = GetTopicRef(nodesAr, m_errTopic);

	// If fail errTopic was returned.
	if(topic.GetTopicName() == STR_ERROR)
	{
		TRACE(_T("CTopicTree::DeleteMediaItem failed.\n"));
		return FALSE;
	}
	// Delete.
	if(!topic.DeleteMediaItem(mediaItem))
	{
		TRACE(_T("CTopicTree::DeleteMediaItem - topic.DeleteMediaItem failed.\n"));
		return FALSE;
	}
	// OK
	if(setResultsTopic)
		m_resultTopic = topic;
	return TRUE;
}

// Called from:
//	CDsLavDoc::DeleteMediaItemMulti()
// Delete multiple media items in nodesAr topic.
BOOL CTopicTree::DeleteMediaItemMulti(const CNodeArray & nodesAr, const CMediaArray & mediaAr,
									  BOOL setResultsTopic)
{
	// Get topic ref.
	CTopic & topic = GetTopicRef(nodesAr, m_errTopic);

	// If fail errTopic was returned.
	if(topic.GetTopicName() == STR_ERROR)
	{
		TRACE(_T("CTopicTree::DeleteMediaItemMulti failed.\n"));
		return FALSE;
	}
	// Delete each mediaAr media item in topic.
	int sz = mediaAr.GetSize();
	for(int i = 0; i < sz; i++)
	{		
		// Delete.
		if(!topic.DeleteMediaItem(mediaAr[i]))
		{
			TRACE(_T("CTopicTree::DeleteMediaItemMulti - topic.DeleteMediaItem failed.\n"));
			return FALSE;
		}
	}
	// OK
	if(setResultsTopic)
		m_resultTopic = topic;
	return TRUE;
}

// Called from:
//	CDsLavDoc::EditMediaItem - new media item.
// Add media item to Topic at nodesAr topic.
BOOL CTopicTree::AddMediaItemToTopic(const CNodeArray & nodesAr, CMediaItem & mediaItem,
									 BOOL setResultsTopic)
{
	// Get topic ref.
	CTopic & topic = GetTopicRef(nodesAr, m_errTopic);

	// If fail errTopic was returned.
	if(topic.GetTopicName() == STR_ERROR)
	{
		TRACE(_T("CTopicTree::AddMediaItemToTopic - GetTopicRef failed.\n"));
		return FALSE;
	}
	// Add media item.
	topic.m_topicMediaAr.Add(mediaItem);

	// OK
	if(setResultsTopic)
		m_resultTopic = topic;
	return TRUE;
}

// Called from:
//	CDsLavDoc::AddMediaFilesToTopic
// Add ar param media items to Topic at nodesAr topic.
BOOL CTopicTree::AddMediaItemsToTopic(const CNodeArray & nodesAr, CMediaArray & ar,
									  BOOL setResultsTopic)
{
	// Ensure items.
	int nItems = ar.GetSize();
	if(!nItems)
		return FALSE;

	// Get topic ref.
	CTopic & topic = GetTopicRef(nodesAr, m_errTopic);

	// If fail errTopic was returned.
	if(topic.GetTopicName() == STR_ERROR)
	{
		TRACE(_T("CTopicTree::AddMediaItemToTopic - GetTopicRef failed.\n"));
		return FALSE;
	}
	// Get a copy for testing dups.
	CMediaArray curAr;
	curAr = topic.m_topicMediaAr;
	CMediaItem item;

	// Add media items.
	for(int i = 0; i < nItems; i++)
	{
		item = ar[i];

		// Check if a duplicate item.
		if(curAr.IsDuplicateItem(item))
			continue;

		// Add to topic.
		topic.m_topicMediaAr.Add(item);
		TRACE(_T("CTopicTree::AddMediaItemsToTopic adding %s\n"), item.m_mediaName);
	}
	// OK
	if(setResultsTopic)
		m_resultTopic = topic;
	return TRUE;
}

void CTopicTree::Read(CArchive & ar, CTopicStrAliasAr & aliasAr, int & version)
{
	m_topicTreeAr.Read(ar, aliasAr, version);
}

void CTopicTree::Write(CArchive & ar, CTopicStrAliasAr & aliasAr, int & version)
{
	m_topicTreeAr.Write(ar, aliasAr, version);
}

///////////////////////////////////////////////////////////////////
// CTopic
CTopic::CTopic()
{
	Zero();
}
CTopic::~CTopic()
{
}
CTopic::CTopic(const CTopic & item) 
{ 
	Copy(item); 
}
void CTopic::operator=(const CTopic & item) 
{ 
	Copy(item); 
};

BOOL CTopic::operator==(const CTopic & item)
{
	if(	m_topicMediaType == item.m_topicMediaType	&&
		m_topicMediaAr	== item.m_topicMediaAr		&&
		m_childTopicAr	== item.m_childTopicAr		&&
		m_nodesAr		== item.m_nodesAr			)
	{
		return TRUE;
	}
	return FALSE;
}

BOOL CTopic::operator!=(const CTopic & item) 
{ 
	return !(*this == item); 
}

void CTopic::Copy(const CTopic & item)
{
	m_topicMediaType = item.m_topicMediaType;
	m_topicMediaAr.Copy(item.m_topicMediaAr);
	m_childTopicAr.Copy(item.m_childTopicAr);
	m_nodesAr.Copy(item.m_nodesAr);
}
	
void CTopic::Zero() 
{
	m_topicMediaType = NONE;
	m_topicMediaAr.RemoveAll();
	m_childTopicAr.RemoveAll();
	m_nodesAr.RemoveAll();
}

// Get all favorites in this topic and its child topics.
BOOL CTopic::GetFavorites(CMediaArray & mediaAr, CNodesArArray & nodesAr, int & nFavsGotten)
{
	// Check for favs in this topics m_topicMediaAr.
	int sz = m_topicMediaAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		if(m_topicMediaAr[i].m_isFavorite)
		{
			mediaAr.Add(m_topicMediaAr[i]);
			nodesAr.Add(m_nodesAr);
			nFavsGotten ++;
		}
	}
	// Check for favs in the m_childTopicAr.
	sz = m_childTopicAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		m_childTopicAr[i].GetFavorites(mediaAr, nodesAr, nFavsGotten);
	}
	return TRUE;
}

// Return a reference to the nodeElmt topic in nodeAr. 
// Returns errTopic param if a failure,
// Searches using CNodeItem indices.
CTopic& CTopic::GetTopicRef(const CNodeArray & nodesAr, int nodeElmt, CTopic & errTopic)
{
	int nNodes = nodesAr.GetSize();
	if(!nNodes)
		return errTopic;

	// Check node ar bounds.
	if(nodeElmt >= nNodes)
	{
		TRACE(_T("CTopic GetTopicRef_ node elmt out of bounds\n"));
		return errTopic;
	}
	// This topic must be a parent in nodesAr.
	BOOL equals;
	if(!IsParentNodeOf(nodesAr, equals))
	{
		TRACE(_T("CTopic GetTopicRef_ topic nodes not a parent in nodesAr.\n"));
		return errTopic;
	}
	// If last node, were done.
	if(nodeElmt + 1 == nNodes)
	{
		// Nodes should match.
		if(!equals)
		{
			TRACE(_T("CTopic GetTopicRef_ nodeAr nodes do not match topic nodes\n"));
			return errTopic;
		}
		// Good.
		return *this; // *this is an object
	}
	// Else check child topics.
	else if(nodeElmt + 1 < nNodes)
	{
		nodeElmt++;

		// Get next node - child.
		CNodeItem nodeItem = nodesAr[nodeElmt];

		// Check bounds.
		int sz = m_childTopicAr.GetSize();
		if(nodeItem.m_arIndex >= sz)
		{
			TRACE(_T("CTopic GetTopicRef_ child node elmt out of bounds\n"));
			return errTopic;
		}
		// Get child topic.
		CTopic & childTopic = m_childTopicAr[nodeItem.m_arIndex];

		// Go to child.
		return childTopic.GetTopicRef(nodesAr, nodeElmt, errTopic);
	}
	// Problem
	return errTopic;
}

// Print media items to debug TRACE.
BOOL CTopic::PrintTopicMediaItems(CTopicStrAliasAr & alias)
{
	// Check topic for media items in this topics m_topicMediaAr.
	CString str;
	int sz = m_topicMediaAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		// Get a ref;
		CMediaItem & item = m_topicMediaAr[i];

		// Print:
		//
		// Code 0 - stream type
		TRACE(_T("0\n"));
		// Type
		TRACE(_T("%s\n"), item.GetStreamTypeDefinitionString());

		// Code 6 - contents node list - optional
		int sz2 = item.m_nodesList.GetSize();
		if(sz)
		{
			TRACE(_T("6\n"));
			// Comma separated string.
			CString str;
			CString strAlias;
			for(int i2 = 0; i2 < sz2; i2++)
			{
				// Print node aliases.
				strAlias = item.m_nodesList[i2];
				alias.GetTopicAlias(strAlias);
				str += strAlias;
				// Print node strings.
				//str += item.m_nodesList[i2];
				if(i2 + 1 < sz2)
					str += _T(",");
			}
			TRACE(_T("%s\n"), str);
		}

		// Code 7 - favorites - optional
		if(item.m_isFavorite)
		{
			TRACE(_T("7\n"));
			TRACE(_T("yes\n"));
		}

		// Code 10 - media name
		TRACE(_T("10\n"));
		TRACE(_T("%s\n"), item.m_mediaName);

		// Code 11 - url
		TRACE(_T("11\n"));
		TRACE(_T("%s\n"), item.m_mediaUrl);

		// Blank line.
		TRACE(_T("\n"));
	}

	// Check for media items in the m_childTopicAr.
	sz = m_childTopicAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		m_childTopicAr[i].PrintTopicMediaItems(alias);
	}
	return TRUE;
}

// Count topics and media items.
BOOL CTopic::CountTopicsMediaItems(int & nTopics, int & nMediaItems)
{
	// Count media items in this topics m_topicMediaAr.
	CString str;
	int sz = m_topicMediaAr.GetSize();
	nMediaItems += sz;

	// Count topics and media items in the m_childTopicAr.
	sz = m_childTopicAr.GetSize();
	nTopics += sz;
	for(int i = 0; i < sz; i++)
	{
		m_childTopicAr[i].CountTopicsMediaItems(nTopics, nMediaItems);
	}
	return TRUE;
}

// Write to file in DsLav Media Record Format.
// Unicode file capable via CStdioFileEx.
BOOL CTopic::WriteMediaRecordFormatEx(CStdioFileEx & file, CTopicStrAliasAr & alias, 
									  BOOL & excludeDisc, BOOL & writeAlias, int & nWritten)
{
	// Check topic for media items in this topics m_topicMediaAr.
	//
	// Note CStdioFileEx does not write the carriage return–linefeed pairs
	// for '\n' - we must do it ourselves.
	CString newline = _T("\r\n");

	CString fileStr;
	int sz = m_topicMediaAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		// Get a ref;
		CMediaItem & item = m_topicMediaAr[i];

		// If not writing disc files.
		//  i.e. C:\Program Files\....\media\house.wma
		if(excludeDisc)
		{
			if( (item.m_mediaType == VIDF || item.m_mediaType == AUDF) &&
				item.m_mediaUrl.Find(_T(":\\"), 0) != -1 )
			{
				continue;
			}
		}

		// Write to file:
		//
		// Code 0 - stream type
		fileStr = _T("0");
		file.WriteString(fileStr);
		file.WriteString(newline);
		// Type
		fileStr.Format(_T("%s"), item.GetStreamTypeDefinitionString());
		file.WriteString(fileStr);
		file.WriteString(newline);

		// Code 6 - contents topic node list - optional
		int sz2 = item.m_nodesList.GetSize();
		if(sz2)
		{
			// Code 6
			fileStr =_T("6");
			file.WriteString(fileStr);
			file.WriteString(newline);

			// Comma separated topic strings.
			CString csStr;
			CString strNode;
			for(int i2 = 0; i2 < sz2; i2++)
			{
				strNode = item.m_nodesList[i2];

				// If writing node aliases.
				if(writeAlias)
					alias.GetTopicAlias(strNode);

				// Add to CS list.
				csStr += strNode;

				// Add comma if not last.
				if(i2 + 1 < sz2)
					csStr += _T(",");
			}
			file.WriteString(csStr);
			file.WriteString(newline);
		}

		// Code 7 - favorites - optional
		if(item.m_isFavorite)
		{
			fileStr = _T("7");
			file.WriteString(fileStr);
			file.WriteString(newline);
			fileStr = _T("yes");
			file.WriteString(fileStr);
			file.WriteString(newline);
		}

		// Code 10 - media name
		fileStr = _T("10");
		file.WriteString(fileStr);
		file.WriteString(newline);
		fileStr.Format(_T("%s"), item.m_mediaName);
		file.WriteString(fileStr);
		file.WriteString(newline);

		// Code 11 - url
		fileStr = _T("11");
		file.WriteString(fileStr);
		file.WriteString(newline);
		fileStr.Format(_T("%s"), item.m_mediaUrl);
		file.WriteString(fileStr);
		file.WriteString(newline);

		// Blank line.
		file.WriteString(newline);

		// Increment.
		nWritten++;
	}

	// Check this topics child topics for media items.
	int sz3 = m_childTopicAr.GetSize();
	for(int i3 = 0; i3 < sz3; i3++)
	{
		m_childTopicAr[i3].WriteMediaRecordFormatEx(file, alias, excludeDisc, 
												   writeAlias, nWritten);
	}
	return TRUE;
}

// Write streams to file in M3u Format. Disc files not written.
// Unicode file capable via CStdioFileEx.
BOOL CTopic::WriteM3uStreamsEx(CStdioFileEx & file, int & nWritten)
{
	// Check topic for media items in this topics m_topicMediaAr.
	//
	// Note CStdioFileEx does not write the carriage return–linefeed pairs
	// for '\n' - we must do it ourselves.
	CString newline = _T("\r\n");

	// Prefix at title.
	CString titlePrefix = _T("#EXTINF:-1,");

	CString fileStr;
	int sz = m_topicMediaAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		// Get a ref;
		CMediaItem & item = m_topicMediaAr[i];

		// Not writing disc files.
		//  i.e. C:\Program Files\....\media\house.wma
		if( (item.m_mediaType == VIDF || item.m_mediaType == AUDF) &&
			item.m_mediaUrl.Find(_T(":\\"), 0) != -1 )
		{
			continue;
		}

		// Write to file:
		//
		// Title:
		fileStr = titlePrefix;
		fileStr += item.m_mediaName;
		file.WriteString(fileStr);
		file.WriteString(newline);

		// Url:
		fileStr = item.m_mediaUrl;
		file.WriteString(fileStr);
		file.WriteString(newline);

		// Blank line.
		file.WriteString(newline);

		// Increment.
		nWritten++;
	}

	// Check for media items in the m_childTopicAr.
	sz = m_childTopicAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		m_childTopicAr[i].WriteM3uStreamsEx(file, nWritten);
	}
	return TRUE;
}

// Get all CMediaItems in this topic and its children.
BOOL CTopic::GetTopicMediaItems(CMediaArray & mediaAr)
{
	// Fect each media item.
	int sz = m_topicMediaAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		// Get a ref;
		CMediaItem & item = m_topicMediaAr[i];

		// Add
		mediaAr.Add(item);
	}

	// Check for media items in the m_childTopicAr.
	sz = m_childTopicAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		m_childTopicAr[i].GetTopicMediaItems(mediaAr);
	}
	return TRUE;
}

// Delete a media item from this topic
BOOL CTopic::DeleteMediaItem(const CMediaItem & mediaItem)
{
	// Delete mediaItemSrc from topic.
	int iDelete = -1;
	int sz = m_topicMediaAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		CMediaItem & item = m_topicMediaAr[i];
		if(item == mediaItem)
		{
			iDelete = i;
			break;
		}
	}
	if(iDelete == -1)
	{
		TRACE(_T("CTopic::DeleteMediaItem could not delete media item.\n"));
		return FALSE;
	}
	else
		m_topicMediaAr.RemoveAt(iDelete);
	// OK.
	return TRUE;
}

// The listElmt param is a child topic of this topic.
BOOL CTopic::GetCreateTopicNodes(const CStringArray & nodesList, const MEDIA_TYPE & type,
								 int listElmt, CNodeArray & nodesAr)
{
	int nListNodes = nodesList.GetSize();

	// Check nodesList bounds.
	if(listElmt >= nListNodes)
	{
		TRACE(_T("CTopic::GetCreateTopicNodes listElmt out of bounds.\n"));
		return FALSE;
	}
	// Ensure this Topic is the parent node of MediaItem.
	BOOL equals;
	if(!IsParentNodeOf(nodesList, equals))
	{
		TRACE(_T("CTopic::GetCreateTopicNodes this topic not parent of strAr.\n"));
		return FALSE;
	}
	// Check if the child topic at listElmt exists.
	CString topicName = nodesList[listElmt];
	int iElmt = -1;
	int nChildTopics = m_childTopicAr.GetSize();
	for(int i = 0; i < nChildTopics; i++)
	{
		if(m_childTopicAr[i].GetTopicName() == topicName)
		{
			iElmt = i;
			break;
		}
	}
	// If no child topic, create one.
	if(iElmt == -1)
	{
		// Add new empty child topic.
		CTopic childTopic;
		iElmt = m_childTopicAr.Add(childTopic);

		// Set child nodes and Type.
		//
		CTopic & child = m_childTopicAr[iElmt];

		// The child inherits parent nodes.
		child.m_nodesAr = m_nodesAr;

		// Add the child's topic node to the end.
		CNodeItem node;
		node.m_topicName = topicName;
		node.m_arIndex = iElmt;
		child.m_nodesAr.Add(node);
		// Set type.
		child.m_topicMediaType = type;
	}
	// The new or existing child topic's m_nodesAr contains 
	// the child's node and all parent nodes gotten so far. 
	// Set nodesAr param to match.
	//
	// Get child.
	CTopic & childTopic = m_childTopicAr[iElmt];
	// Set param
	nodesAr = childTopic.m_nodesAr;

	// If last topic in MediaItem, we are done.
	if(listElmt + 1 == nListNodes)
	{
		return TRUE;
	}
	// Else if more MediaItem topics.
	else if(listElmt + 1 < nListNodes)
	{
		// Next list node.
		listElmt ++;
		// Get next child.
		return childTopic.GetCreateTopicNodes(nodesList, type, listElmt, nodesAr);
	}
	// Problem.
	return FALSE;
}

// Is this Topic the parent node of CNodeAr param.
// Equals param is set TRUE if nodes match.
BOOL CTopic::IsParentNodeOf(const CNodeArray & ar, BOOL & equals)
{
	return m_nodesAr.IsParentNodeOf(ar, equals);
}

// Is this Topic the parent node of CStringArray param.
// Equals param is set TRUE if nodes match.
BOOL CTopic::IsParentNodeOf(const CStringArray & ar, BOOL & equals)
{
	return m_nodesAr.IsParentNodeOf(ar, equals);
}

// Get this topics TopicName - the last node in m_nodesAr.
// Returns _T("") if empty.
CString CTopic::GetTopicName()
{
	return m_nodesAr.GetTopicName();
}

// Get type as a CString.
CString CTopic::GetTopicTypeString()
{
	// Use CMediaItem functions
	CMediaItem item;
	item.m_mediaType = m_topicMediaType;
	return item.GetStreamTypeString();
}


// Set this topics TopicName - the last node in m_nodesAr.
void CTopic::SetTopicName(CString str)
{
	m_nodesAr.UpdateTopicName(str);
}

// Scenario:
// A topic has been moved to a new location.
// The topic's m_nodesAr has been updated for
// the new location, but its child topic
// nodes and Media item nodesList have not. 
// This will update them all.
BOOL CTopic::ResetChildTopicAndMediaNodes(int & nResets)
{
	CStringArray strAr;
	CString name; // dbg
	int sz = m_childTopicAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		// Get a ref to child.
		CTopic & topic = m_childTopicAr[i];
		name = topic.GetTopicName(); // dbg

		// Save the child topic's topic node - last in array
		CNodeItem childTopicNode;
		if(!topic.m_nodesAr.GetTopicNodeItem(childTopicNode))
		{
			TRACE(_T("CTopic::ResetChildTopicNodes GetTopicNodeItem failed.\n"));
			return FALSE;
		}
		// Set child topic nodes to parents, plus
		// the topic node saved above, along with 
		// the current ar index.
		topic.m_nodesAr = m_nodesAr;
		childTopicNode.m_arIndex = i;
		topic.m_nodesAr.Add(childTopicNode);
		nResets++;

		// Update the child topic media's topic node strings.
		int nMedia = topic.m_topicMediaAr.GetSize();
		if(nMedia)
		{
			// Get Topic nodes as a CStringArray.
			topic.m_nodesAr.GetTopicNameStrings(strAr);
			// Reset Media nodes to parent nodes.
			for(int i = 0; i < nMedia; i++)
			{
				topic.m_topicMediaAr[i].m_nodesList.Copy(strAr);
				nResets++;
			}
		}
		// If this child topic has children.
		if(topic.m_childTopicAr.GetSize())
		{
			if(!topic.ResetChildTopicAndMediaNodes(nResets))
			{
				TRACE(_T("CTopic::ResetChildTopicNodes reset child's children failed.\n"));
				return FALSE;
			}
		}
	}
	return TRUE;
}

void CTopic::Read(CArchive & ar, CTopicStrAliasAr & aliasAr, int & version)
{
	m_nodesAr.Read(ar, aliasAr, version);
	ar.Read(&m_topicMediaType, sizeof(MEDIA_TYPE)); // enum
	m_topicMediaAr.Read(ar, aliasAr, version);
	m_childTopicAr.Read(ar, aliasAr, version);
	TRACE(_T("CTopic::Read Topic Name %s\n"), GetTopicName());
}

void CTopic::Write(CArchive & ar, CTopicStrAliasAr & aliasAr, int & version)
{
	TRACE(_T("CTopic::Write Topic Name %s\n"), GetTopicName());
	m_nodesAr.Write(ar, aliasAr, version);
	ar.Write(&m_topicMediaType, sizeof(MEDIA_TYPE)); // enum
	m_topicMediaAr.Write(ar, aliasAr, version);
	m_childTopicAr.Write(ar, aliasAr, version);
}

///////////////////////////////////////////////////////////////////
// CTopicArray

// Must implement here for CTopic != def.
BOOL CTopicArray::operator==(const CTopicArray & ar)
{
	int sz = GetSize();
	for(int i = 0; i < sz; i++)
	{
		if(GetAt(i) != ar.GetAt(i))
			return FALSE;
	}
	return TRUE;
};

void CTopicArray::Read(CArchive & ar, CTopicStrAliasAr & aliasAr, int & version)
{
	CTopic topic;
	int sz;
	ar >> sz;
	for(int i = 0; i < sz; i++)
	{
		topic.Zero();
		topic.Read(ar, aliasAr, version);
		Add(topic);
	}
}

void CTopicArray::Write(CArchive & ar, CTopicStrAliasAr & aliasAr, int & version)
{
	int sz = GetSize();
	ar << sz;
	for(int i = 0; i < sz; i++)
	{
		GetAt(i).Write(ar, aliasAr, version);
	}
}

///////////////////////////////////////////////////////////////////
// CMediaItem
CMediaItem::CMediaItem()
{
	Zero();
}
CMediaItem::~CMediaItem()
{
}
CMediaItem::CMediaItem(const CMediaItem & item) 
{ 
	Copy(item); 
}
void CMediaItem::operator=(const CMediaItem & item) 
{ 
	Copy(item); 
};
BOOL CMediaItem::operator==(const CMediaItem & item)
{
	if(	m_mediaName  == item.m_mediaName &&
		m_mediaUrl   == item.m_mediaUrl  &&
		m_mediaType  == item.m_mediaType  ) //&&
//		m_isFavorite == item.m_isFavorite ) ignore as it can be true or false
	{
		int sz = m_nodesList.GetSize();
		if(item.m_nodesList.GetSize() != sz)
			return FALSE;
		for(int i = 0; i < sz; i++)
		{
			if(m_nodesList[i] != item.m_nodesList[i])
				return FALSE;
		}
		// OK
		return TRUE;
	}
	return FALSE;
}
BOOL CMediaItem::operator!=(const CMediaItem & item) 
{ 
	return !(*this == item); 
}
void CMediaItem::Copy(const CMediaItem & item)
{
	m_mediaType			= item.m_mediaType;
	m_mediaName			= item.m_mediaName;
	m_mediaUrl			= item.m_mediaUrl;
	m_nodesList.Copy(item.m_nodesList);
	m_isFavorite		= item.m_isFavorite;
}	
void CMediaItem::Zero() 
{
	m_mediaType = NONE;
	m_mediaName.Empty();
	m_mediaUrl.Empty();
	m_nodesList.RemoveAll();
	m_isFavorite = FALSE;
}

// Can this item be used?
BOOL CMediaItem::IsValidItem()
{
	// Must.
	if(m_mediaType == NONE)
		return FALSE;
	if(m_mediaUrl.IsEmpty())
		return FALSE;
	if(m_mediaName.IsEmpty())
		return FALSE;
	// OK
	return TRUE;
}

CString CMediaItem::GetStreamTypeString()
{
	/*MEDIA_TYPE{NONE,	// empty
				AUDU,	// audio url		- http, https
				VIDU,	// video url		- http, https
				VIDF,	// video file	- mp4,
				AUDF 	// audio file	- mp3,*/
	switch(m_mediaType)
	{
		case NONE:
			return _T("Unknown Stream");
		case AUDU:
			return _T("Audio Stream");
		case VIDU:
			return _T("Video Stream");
		case VIDF:
			return _T("Video File");
		case AUDF:
			return _T("Audio File");
		default:
			return _T("Unknown Stream");
	}
}
// Get items MEDIA_TYPE as a string.
CString CMediaItem::GetStreamTypeDefinitionString()
{
	switch(m_mediaType)
	{
		case NONE:
			return _T("NONE");
		case AUDU:
			return _T("AUDU");
		case VIDU:
			return _T("VIDU");
		case VIDF:
			return _T("VIDF");
		case AUDF:
			return _T("AUDF");
		default:
			return _T("Unknown Stream");
	}
}

MEDIA_TYPE CMediaItem::GetMediaTypeFromString(CString typeStr)
{
	MEDIA_TYPE type = NONE;

	if(typeStr == _T("NONE"))
		return NONE;
	else if(typeStr == _T("AUDU"))
		return AUDU;
	else if(typeStr == _T("VIDU"))
		return VIDU;
	else if(typeStr == _T("VIDF"))
		return VIDF;
	else if(typeStr == _T("AUDF"))
		return AUDF;

	// Problems
	TRACE(_T("ERROR CMediaItem::GetMediaTypeFromString returning NONE\n"));
	return type;
}

CString CMediaItem::GetMediaTopicName()
{
	int sz = m_nodesList.GetSize();
	if(!sz)
	{
		CString node = GetDefaultMediaTopicName();
		m_nodesList.Add(node);
		return node;		
	}
	// Last in list is the topic name.
	return m_nodesList[sz - 1];
}

CString CMediaItem::GetDefaultMediaTopicName()
{
	switch(m_mediaType)
	{
		case AUDU:
			return _T("Audio Streams");
		case VIDU:
			return _T("Video Streams");
		case VIDF:
			return _T("Video Files");
		case AUDF:
			return _T("Audio Files");
		default:
			return _T("Miscellaneous"); // Problem for playing media. 12/22-5 fix misspell
	}
}

// CsNodes str is a string of comma separated node strings.
// Note: Conversion of nodes that are alias' to full 
// topic strings is done by caller.
BOOL CMediaItem::SetMediaItem(MEDIA_TYPE mediaType, CString mediaName, 
							  CString mediaUrl, BOOL isFavorite, CString CsNodes)
{
	if(CsNodes.IsEmpty())
		return FALSE;

	// Type.
	m_mediaType = mediaType;
	// Name.
	m_mediaName = mediaName;
	// Url.
	m_mediaUrl = mediaUrl;
	// Favs
	m_isFavorite = isFavorite;

	// Parse CsNodes string.
	TCHAR comma = _T(',');
	CString node, str = CsNodes;
	int nPos = 0;

	while(str.GetLength() > 0)
	{
		nPos = str.Find(comma);
		if(nPos == -1)
		{
			str.TrimLeft();
			str.TrimRight();
			m_nodesList.Add(str);
			str.Empty();
		}
		else
		{
			node = str.Left(nPos);
			node.TrimLeft();
			node.TrimRight();
			m_nodesList.Add(node);
			// Get remainder of string after comma.
			str = str.Mid(nPos + 1);
		}
	}
	return TRUE;
}

// CsNodes str is a string of comma separated node strings.
// Note: Conversion of nodes that are alias' to full 
// topic strings is done by caller.
BOOL CMediaItem::SetMediaItem(MEDIA_TYPE mediaType, CString mediaName, 
							  CString mediaUrl, BOOL isFavorite, 
							  CString CsNodes, CTopicStrAliasAr * pAliasAr)
{
	if(CsNodes.IsEmpty())
		return FALSE;
	if(pAliasAr == NULL)
		return FALSE;

	// Type.
	m_mediaType = mediaType;
	// Name.
	m_mediaName = mediaName;
	// Url.
	m_mediaUrl = mediaUrl;
	// Favs
	m_isFavorite = isFavorite;

	// Parse CsNodes string.
	TCHAR comma = _T(',');
	CString node, str = CsNodes;
	int nPos = 0;

	while(str.GetLength() > 0)
	{
		nPos = str.Find(comma);
		if(nPos == -1)
		{
			str.TrimLeft();
			str.TrimRight();
			pAliasAr->GetTopicString(str); // convert from alias as needed
			m_nodesList.Add(str);
			str.Empty();
		}
		else
		{
			node = str.Left(nPos);
			node.TrimLeft();
			node.TrimRight();
			pAliasAr->GetTopicString(node);
			m_nodesList.Add(node);
			// Get remainder of string after comma.
			str = str.Mid(nPos + 1);
		}
	}
	return TRUE;
}

// Set nodes for this Media Item. CsNodes str is a string of comma 
// separated node strings.
// Note: Conversion of nodes that are alias' to full 
// topic strings is done by caller.
BOOL CMediaItem::SetMediaItemTopicNodes(CString CsNodes)
{
	if(CsNodes.IsEmpty())
		return FALSE;

	// Parse CsNodes string.
	TCHAR comma = _T(',');
	CString node, str = CsNodes;
	int nPos = 0;

	while(str.GetLength() > 0)
	{
		nPos = str.Find(comma);
		if(nPos == -1)
		{
			str.TrimLeft();
			str.TrimRight();
			m_nodesList.Add(str);
			str.Empty();
		}
		else
		{
			node = str.Left(nPos);
			node.TrimLeft();
			node.TrimRight();
			m_nodesList.Add(node);
			// Get remainder of string after comma.
			str = str.Mid(nPos + 1);
		}
	}
	return TRUE;
}

// Set nodes for this Media Item. CsNodes str is a string of comma 
// separated node strings.
// Note: Conversion of nodes that are alias' to full 
// topic strings is done by caller.
BOOL CMediaItem::SetMediaItemTopicNodes(CString CsNodes, CTopicStrAliasAr * pAliasAr)
{
	if(CsNodes.IsEmpty())
		return FALSE;
	if(pAliasAr == NULL)
		return FALSE;

	// Parse CsNodes string.
	TCHAR comma = _T(',');
	CString node, str = CsNodes;
	int nPos = 0;

	while(str.GetLength() > 0)
	{
		nPos = str.Find(comma);
		if(nPos == -1)
		{
			str.TrimLeft();
			str.TrimRight();
			pAliasAr->GetTopicString(str); // convert from alias as needed
			m_nodesList.Add(str);
			str.Empty();
		}
		else
		{
			node = str.Left(nPos);
			node.TrimLeft();
			node.TrimRight();
			pAliasAr->GetTopicString(node);
			m_nodesList.Add(node);
			// Get remainder of string after comma.
			str = str.Mid(nPos + 1);
		}
	}
	return TRUE;
}

// Get topic nodes as a CString comma separated list.
CString CMediaItem::GetMediaItemTopicCsNodes()
{
	CString str;
	int sz = m_nodesList.GetSize();
	for(int i = 0; i < sz; i++)
	{
		str += m_nodesList[i];
		// Add comma.
		if(i + 1 < sz)
			str += _T(',');
	}
	return str;
}

// Set nodes from file path.
void CMediaItem::SetNodesFromFilePath(CString filePath)
{
	if(filePath.IsEmpty())
		return;

	// Parse path string.
	TCHAR slash = _T('\\');
	CString nodeStr, str = filePath;
	CNodeItem item;
	int nPos = 0;

	while(str.GetLength() > 0)
	{
		nPos = str.Find(slash);
		if(nPos == -1)
		{
			str.TrimLeft();
			str.TrimRight();
			m_nodesList.Add(str);
			str.Empty();
		}
		else
		{
			nodeStr = str.Left(nPos);
			nodeStr.TrimLeft();
			nodeStr.TrimRight();
			m_nodesList.Add(nodeStr);
			// Get remainder of string after comma.
			str = str.Mid(nPos + 1);
		}
	}
}

void CMediaItem::Read(CArchive & ar, CTopicStrAliasAr & aliasAr, int & version)
{
	ar.Read(&m_mediaType, sizeof(MEDIA_TYPE)); // enum
	ar >> m_mediaName;
	ar >> m_mediaUrl;

	//dbg
	if(m_mediaName == _T("19 Cats Who Are Totally Badass.mp4"))
		int g = 0;

	CString str;
	int sz;
	ar >> sz;
	for(int i = 0; i < sz; i++)
	{
		ar >> str;
		// Convert from alias - see Write().
		m_nodesList.Add(aliasAr.AliasToTopicString(str));
	}
	ar >> m_isFavorite;
	//TRACE(_T("CMediaItem::Read Media Name %s\n"), m_mediaName);

}
void CMediaItem::Write(CArchive & ar, CTopicStrAliasAr & aliasAr, int & version)
{
	TRACE(_T("CMediaItem::Write Media Name %s\n"), m_mediaName);
	ar.Write(&m_mediaType, sizeof(MEDIA_TYPE)); // enum
	ar << m_mediaName;
	ar << m_mediaUrl;
	int sz = m_nodesList.GetSize();
	ar << sz;
	for(int i = 0; i < sz; i++)
	{
		// Write as alias to reduce file size.
		ar << aliasAr.TopicStringToAlias(m_nodesList[i]);
	}
	ar << m_isFavorite;
}


/////////////////////////////////////////////////////////
// CMediaItemAr
BOOL CMediaArray::operator==(const CMediaArray & ar)
{
	for(int i = 0; i < GetSize(); i++)
	{
		if(GetAt(i) != ar.GetAt(i))
			return FALSE;
	}
	return TRUE;
};
void CMediaArray::operator=(const CMediaArray & ar)
{
	Copy(ar);
}

// Test if item param is already in the array.
BOOL CMediaArray::IsDuplicateItem(const CMediaItem & item)
{
	int sz = GetSize();
	for(int i = 0; i < sz; i++)
	{
		if(GetAt(i) == item)
			return TRUE;
	}
	return FALSE;
};

void CMediaArray::Read(CArchive & ar, CTopicStrAliasAr & aliasAr, int & version)
{
	CMediaItem item;
	int sz;
	ar >> sz;
	for(int i = 0; i < sz; i++)
	{
		item.Zero();
		item.Read(ar, aliasAr, version);
		Add(item);
	}
}
void CMediaArray::Write(CArchive & ar, CTopicStrAliasAr & aliasAr, int & version)
{
	int sz = GetSize();
	ar << sz;
	for(int i = 0; i < sz; i++)
	{
		GetAt(i).Write(ar, aliasAr, version);
	}
}

// Sort media array by m_mediaName per ascent param.
void CMediaArray::SortMediaName(BOOL ascend)
{
	if(ascend)
		qsort(GetData(), GetSize(), sizeof(CMediaItem), CompareMediaNameAscend);
	else
		qsort(GetData(), GetSize(), sizeof(CMediaItem), CompareMediaNameDescend);
}
// Qsort ascending.
int CMediaArray::CompareMediaNameAscend(const void * arg1, const void * arg2)
{
	// Get the CMediaItems.
	CMediaItem * item1 = (CMediaItem *)arg1;
	CMediaItem * item2 = (CMediaItem *)arg2;
	return item1->m_mediaName.CompareNoCase(item2->m_mediaName);
}
// Qsort descending.
int CMediaArray::CompareMediaNameDescend(const void * arg1, const void * arg2)
{
	// Get the CMediaItems.
	CMediaItem * item1 = (CMediaItem *)arg1;
	CMediaItem * item2 = (CMediaItem *)arg2;
	return item2->m_mediaName.CompareNoCase(item1->m_mediaName);
}



/////////////////////////////////////////////////////////
// CTopicStrAliasAr
// This class is used as a single instance 
// in CDsLavDoc for file I/O.

// Convert a string of comma separated alias's to strings in an array.
//  ar is an array of topic strings converted from the 
//  csStr comma separated list of alias's.
//  Return TRUE if conversion worked.
BOOL CTopicStrAliasAr::ConvertFromCsAlias(CStringArray & ar, CString csStr)
{
	// Parse csStr's alias strings, convert each from a topic alias
	// to a topic string, and add to the topic string array.
	//
	ar.RemoveAll();
	CString strAlias = csStr;	// a copy for modification.
	CString node;
	CString comma = _T(',');
	int nPos = 0;
	while(strAlias.GetLength() > 0)
	{
		nPos = strAlias.Find(comma);

		// Last or only one string.
		if(nPos == -1)
		{
			strAlias.TrimLeft();
			strAlias.TrimRight();
			// Convert.
			GetTopicString(strAlias);
			// Add to array.
			ar.Add(strAlias);
			strAlias.Empty();
		}
		// Multiple strings.
		else
		{
			node = strAlias.Left(nPos);
			node.TrimLeft();
			node.TrimRight();
			// Convert
			GetTopicString(node);
			// Add to array.
			ar.Add(node);
			// Get remainder of string after comma.
			strAlias = strAlias.Mid(nPos + 1);
		}
	}
	if(ar.GetSize())
		return TRUE;
	// No strings added.
	return FALSE;
}
// Convert a CMediaItem's m_nodesList strings from alias to topic strings.
//  Return TRUE if conversion successfull.
BOOL CTopicStrAliasAr::ConvertFromCsAlias(CMediaItem & item)
{
	BOOL ret = TRUE;
	int sz = item.m_nodesList.GetSize();

	// Must have strings to convert.
	if(!sz)
		return FALSE;

	// Convert each string in array from an alias to topic string.
	for(int i = 0; i < sz; i++)
	{
		// dbg
		//CString s = item.m_nodesList[i];

		// Convert in place.
		if(!GetTopicString(item.m_nodesList[i]))
			ret = FALSE;

		// dbg
		//s = item.m_nodesList[i];

	}
	return ret;
}
// Convert strings in a string of comma separated alias's to topic strings.
//  csStr is a comma separated list of alias's to convert in place.
//  Return TRUE if conversion worked.
BOOL CTopicStrAliasAr::ConvertFromCsAlias(CString & csStr)
{
	// Parse csStr's alias strings, convert each from a topic alias
	// to a topic string, then reset csStr with new topic strings. 
	//
	CString strAlias = csStr;	// a copy for modification.
	CString comma = _T(',');
	CString newCsStr;
	CString str;
	int nPos = 0;
	while(strAlias.GetLength() > 0)
	{
		nPos = strAlias.Find(comma);

		// Last or only one string.
		if(nPos == -1)
		{
			strAlias.TrimLeft();
			strAlias.TrimRight();
			// Convert.
			GetTopicString(strAlias);

			// Add to newCsStr.
			newCsStr += strAlias;

			// Done.
			strAlias.Empty();
		}
		// Multiple strings.
		else
		{
			str = strAlias.Left(nPos);
			str.TrimLeft();
			str.TrimRight();
			// Convert
			GetTopicString(str);

			// Add to newCsStr.
			newCsStr += str;
			// Add comma after alias string - except the last.
			newCsStr += _T(',');
		}
	}
	// Set csStr param.
	csStr = newCsStr;
	return TRUE;
}

// Convert a string array to a comma separated string of alias's.
//  ar is an array of topic strings.
//  cstr is the array strings converted to a Comma Separated list of alias's.
//  Return TRUE if conversion successfull.
BOOL CTopicStrAliasAr::ConvertToCsAlias(const CStringArray & ar, CString & csStr)
{
	int sz = ar.GetSize();

	// Must have strings to convert.
	if(!sz)
		return FALSE;

	csStr.Empty();
	CString strAlias;
	for(int i = 0; i < sz; i++)
	{
		// Convert to alias.
		strAlias = ar[i];
		GetTopicAlias(strAlias);

		// Add to array.
		csStr += strAlias;
		// Add comma after alias string - except the last.
		if(i != sz - 1)
			csStr += _T(',');
	}
	return FALSE;
}

// Add a TOPIC_STR_ALIAS to this array.
BOOL CTopicStrAliasAr::AddTopicAlias(CString alias, CString str)
{
	TOPIC_STR_ALIAS tsa;
	tsa.str = str;
	tsa.alias = alias;
	Add(tsa);
	return TRUE;
}

// IN:  str is a full topic name string.
// OUT: str is initialized with an alias string if one exists.
// Returns TRUE if a Topic Alias was available.
BOOL CTopicStrAliasAr::GetTopicAlias(CString & str)
{
	int sz = GetSize();
	for(int i = 0; i < sz; i++)
	{
		if(GetAt(i).str == str)
		{
			str = GetAt(i).alias;
			return TRUE;
		}
	}
	return FALSE;
}

// IN:  str is a topic name alias.
// OUT: str is initialized with a topic string if one exists.
// Returns TRUE if a Topic String was available.
BOOL CTopicStrAliasAr::GetTopicString(CString & str)
{
	int sz = GetSize();
	for(int i = 0; i < sz; i++)
	{
		if(GetAt(i).alias == str)
		{
			str = GetAt(i).str;
			return TRUE;
		}
	}
	return FALSE;
}

// str param is a full topic name string.
// Returns a topic alias if available, or 
// the str param if none.
CString CTopicStrAliasAr::TopicStringToAlias(CString & str)
{
	int sz = GetSize();
	for(int i = 0; i < sz; i++)
	{
		if(GetAt(i).str == str)
		{
			return GetAt(i).alias;
		}
	}
	return str;
}

// alias param is a presumed topic alias.
// Returns a topic string if available, or 
// the alias param if none.
CString CTopicStrAliasAr::AliasToTopicString(CString & alias)
{
	int sz = GetSize();
	for(int i = 0; i < sz; i++)
	{
		if(GetAt(i).alias == alias)
		{
			return GetAt(i).str;
		}
	}
	return alias;
}

// Lode this array with TOPIC_STR_ALIAS's
// from TopicAlias.txt disc file.
BOOL CTopicStrAliasAr::InitializeTopicAlias()
{
	// Get current directory.
	CString curDir = ((CDsLavApp*)AfxGetApp())->m_curDirectory;
	CString filePath = curDir;
	// Add file name.
	filePath += _T("\\TopicAlias.txt");

	// Open TopicAlias.txt. 
	CStdioFile file;
	CFileException e;
	if(!file.Open(filePath, 
				  CFile::modeRead | CFile::typeText,
				  &e)) 
	{
		TRACE(_T("CTopicStrAliasAr::InitializeTopicAlias - can not open file %s, error = %u\n"), 
					filePath, e.m_cause );
		return FALSE;
	}
	// Clean array.
	RemoveAll();

	CString fileStr;
	CString alias, topic;

	// Read the disk file.
	while(file.ReadString(fileStr))
	{
		// Empty line.
		if(fileStr == _T(""))
			continue;

		// Comment.
		if(fileStr.Find(_T("//")) != -1)
			continue;

		// Get the pair.
		int nPos = fileStr.Find(_T(','));
		if(nPos == -1)
			continue;
		int nChars = fileStr.GetLength();
		alias = fileStr.Left(nPos);
		topic = fileStr.Right(nChars - nPos - 1);

		// Add to array.
		AddTopicAlias(alias, topic);		
	}
	// Done.
	// dbg
	int nAliases = GetSize();//23
	file.Close();
	return TRUE;
}

//////////////////////////////////////////////////////
// CNodeItem
CNodeItem::CNodeItem()
{
	Zero();
}
CNodeItem::~CNodeItem()
{
}
CNodeItem::CNodeItem(const CNodeItem & nodeItem)
{
	m_topicName = nodeItem.m_topicName;
	m_arIndex = nodeItem.m_arIndex;
}
void CNodeItem::operator=(const CNodeItem & nodeItem)
{
	m_topicName = nodeItem.m_topicName;
	m_arIndex = nodeItem.m_arIndex;
}
BOOL CNodeItem::operator==(const CNodeItem & nodeItem)
{
	if(m_topicName == nodeItem.m_topicName && m_arIndex == nodeItem.m_arIndex)
		return TRUE;
	return FALSE;
}
BOOL CNodeItem::operator!=(const CNodeItem & nodeItem)
{
	return !(*this == nodeItem); 
}
void CNodeItem::Zero()
{
	m_topicName =_T("");
	m_arIndex = -1;
}
BOOL CNodeItem::IsValid() const
{
	if(!m_topicName.IsEmpty() && m_arIndex > -1)
		return TRUE;
	else
		return FALSE;
};
void CNodeItem::Read(CArchive & ar, CTopicStrAliasAr & aliasAr, int & version)
{
	// Convert from alias - see Write().
	CString str;
	ar >> str;
	m_topicName = aliasAr.AliasToTopicString(str);
	ar >> m_arIndex;
}
void CNodeItem::Write(CArchive & ar, CTopicStrAliasAr & aliasAr, int & version)
{
	// Write as alias to reduce file size.
	ar << aliasAr.TopicStringToAlias(m_topicName);
	ar << m_arIndex;
}

void CNodeItem::Test(CTopicStrAliasAr & ar)
{
	CString str = _T("Audio Streams");
	ar.GetTopicAlias(str);
}

//////////////////////////////////////////////////////
// CNodeArray

CNodeArray::CNodeArray()
{
}
CNodeArray::~CNodeArray()
{
}
CNodeArray::CNodeArray(const CNodeArray & ar)
{
	CopyNodeArray(ar);
}
void CNodeArray::CopyNodeArray(const CNodeArray & ar)
{
	Copy(ar);
}
void CNodeArray::operator=(const CNodeArray & ar)
{
	Copy(ar);
}
BOOL CNodeArray::operator==(const CNodeArray & ar)
{
	int sz = GetSize();
	int szAr = ar.GetSize();
	if(sz != szAr)
		return FALSE;
	for(int i = 0; i < sz; i++)
	{
		if(GetAt(i) != ar.GetAt(i))
			return FALSE;
	}
	return TRUE;
}
BOOL CNodeArray::operator!=(const CNodeArray & ar)
{
	return !(*this == ar); 
}

// Get node item Topic Names into strAr.
void CNodeArray::GetTopicNameStrings(CStringArray & strAr) const // const no mod CNodeArray
{
	strAr.RemoveAll();
	int sz = GetSize();
	CString str;
	for(int i = 0; i < sz; i++)
	{
		CNodeItem item = GetAt(i);
		strAr.Add(item.m_topicName);
	}
}

// Get nodes as a file path.
// The path does not end with a backslash.
CString CNodeArray::GetFilePathFromNodes() const // const no mod CNodeArray
{
	CString strPath;
	int sz = GetSize();
	for(int i = 0; i < sz; i++)
	{
		CNodeItem item = GetAt(i);
		strPath += item.m_topicName;
		if(i < sz - 1)
			strPath += _T("\\");
	}
	return strPath;
}

// Set nodes from file path.
void CNodeArray::SetNodesFromFilePath(CString filePath)
{
	if(filePath.IsEmpty())
		return;

	// Parse path string.
	TCHAR slash = _T('\\');
	CString nodeStr, str = filePath;
	CNodeItem item;
	int nPos = 0;

	while(str.GetLength() > 0)
	{
		nPos = str.Find(slash);
		if(nPos == -1)
		{
			str.TrimLeft();
			str.TrimRight();
			item.m_topicName = str;
			Add(item);
			str.Empty();
		}
		else
		{
			nodeStr = str.Left(nPos);
			nodeStr.TrimLeft();
			nodeStr.TrimRight();
			item.m_topicName = nodeStr;
			Add(item);
			// Get remainder of string after comma.
			str = str.Mid(nPos + 1);
		}
	}
}

// 9/22-1
// Is this nodeArray a sibling node of CNodeArray param.
BOOL CNodeArray::IsSiblingNodeOf(const CNodeArray & ar, BOOL & bAreRoots) const
{
	int nNodes = GetSize();
	int nNodesParam = ar.GetSize();
	bAreRoots = FALSE;

	// If no nodes, cannot be parent of param.
	if(!nNodes || !nNodesParam)
		return FALSE;

	// This node list is larger or smaller - cannot be sibling of param.
	if(nNodes != nNodesParam)
		return FALSE;

	// If each 1 node - roots.
	if(nNodes == 1)
	{
		bAreRoots = TRUE;
		// Are siblings.
		return TRUE;
	}

	// Test each node less the last - should all be equal.
	for(int i = 0; i < nNodes - 1; i++)
	{
		CNodeItem item1 = GetAt(i);
		CNodeItem item2 = ar.GetAt(i);
		if(item1 != item2)
			return FALSE;

	//	if(GetAt(i) != ar.GetAt(i))
	//		return FALSE;
	}
	return TRUE;
}

// Is this nodeArray the parent node of CNodeArray param.
// If all nodes match, equals gets set TRUE,
BOOL CNodeArray::IsParentNodeOf(const CNodeArray & ar, BOOL & equals) const // 9/22-1 const, changed return type
{
	int nNodes = GetSize();
	int nNodesParam = ar.GetSize();
	equals = FALSE;

	/*dbg 9/22-1
	if(nNodes != nNodesParam)
		int g = 0;*/

	// If no nodes, cannot be parent of param.
	if(!nNodes || !nNodesParam)
		return FALSE;

	// This node list is larger - cannot be parent of param.
	if(nNodes > nNodesParam)
		return FALSE;

	// Test each node.
	for(int i = 0; i < nNodes; i++)
	{
		CNodeItem item1 = GetAt(i);
		CNodeItem item2 = ar.GetAt(i);
		if(item1 != item2)
			return FALSE;

	//	if(GetAt(i) != ar.GetAt(i))
	//		return FALSE;
	}
	// OK
	if(nNodes == nNodesParam)		// equals
		equals = TRUE;

	return TRUE;
}

// Is this nodeArray the parent node of CStringArray param.
// If all nodes match, equals gets set TRUE,
BOOL CNodeArray::IsParentNodeOf(const CStringArray & ar, BOOL & equals) const // 9/22-1 const, changed return type
{
	int nNodes = GetSize();
	int nNodesParam = ar.GetSize();
	equals = FALSE;

	// If no nodes, cannot be parent of param.
	if(!nNodes || !nNodesParam)
		return FALSE;

	// This node list is larger - cannot be parent of param.
	if(nNodes > nNodesParam)
		return FALSE;

	// Test each node.
	for(int i = 0; i < nNodes; i++)
	{
		if(GetAt(i).m_topicName != ar.GetAt(i))
			return FALSE;
	}
	// OK
	if(nNodes == nNodesParam)		// equals
		equals = TRUE;
	return TRUE;
}

// Is this nodeArray the parent node of CNodeArray param.
// If all nodes match, equals gets set TRUE,
int CNodeArray::IsParentNodeOf(const CNodeArray & ar, BOOL & equals)
{
	int nNodes = GetSize();
	int nNodesParam = ar.GetSize();
	equals = FALSE;

	// If no nodes, cannot be parent of param.
	if(!nNodes || !nNodesParam)
		return FALSE;

	// This node list is larger - cannot be parent of param.
	if(nNodes > nNodesParam)
		return FALSE;

	// Test each node.
	for(int i = 0; i < nNodes; i++)
	{
		if(GetAt(i) != ar.GetAt(i))
			return FALSE;
	}
	// OK
	if(nNodes == nNodesParam)		// equals
		equals = TRUE;
	return TRUE;
}

// Is this nodeArray the parent node of CStringArray param.
// If all nodes match, equals gets set TRUE,
int CNodeArray::IsParentNodeOf(const CStringArray & ar, BOOL & equals)
{
	int nNodes = GetSize();
	int nNodesParam = ar.GetSize();
	equals = FALSE;

	// If no nodes, cannot be parent of param.
	if(!nNodes || !nNodesParam)
		return FALSE;

	// This node list is larger - cannot be parent of param.
	if(nNodes > nNodesParam)
		return FALSE;

	// Test each node.
	for(int i = 0; i < nNodes; i++)
	{
		if(GetAt(i).m_topicName != ar.GetAt(i))
			return FALSE;
	}
	// OK
	if(nNodes == nNodesParam)		// equals
		equals = TRUE;
	return TRUE;
}

// Get parent nodes into parentAr param - all nodes 
// but the last in this CNodeArray.
// If no parent nodes, parentAr param equals this.
// Returns FALSE if this CNodeArray is empty.
BOOL CNodeArray::GetParentNodes(CNodeArray & parentAr) const
{
	int sz = GetSize();
	// If empty.
	if(!sz)
		return FALSE;
	parentAr = *this;
	if(sz > 1)
		parentAr.RemoveAt(sz - 1);
	return TRUE;
}

// Get Topic Node into topicItem param - the last in array.
// Returns FALSE if this CNodeArray is empty.
BOOL CNodeArray::GetTopicNodeItem(CNodeItem & topicItem) const
{
	CNodeItem node;
	int sz = GetSize();
	// If empty.
	if(!sz)
		return FALSE;
	else
		topicItem = GetAt(sz - 1);
	return TRUE;
}

// Get the TopicName - last or only in array.
// Returns _T("") if empty.
CString CNodeArray::GetTopicName() const
{
	CString str;
	int nNodes = GetSize();
	if(nNodes)
		return GetAt(nNodes - 1).m_topicName;
	else
		return str;
}

// Reset the nodeAr's Topic Name - last topic in array,
// Adds new node if array empty.
void CNodeArray::UpdateTopicName(CString & topicName)
{
	int nNodes = GetSize();
	CNodeItem item;
	if(nNodes)
	{
		item = GetAt(nNodes - 1);
		item.m_topicName = topicName;
		SetAt(nNodes - 1, item); 
	}
	else
	{
		// Default item index == -1.
		item.m_topicName = topicName;
		Add(item);
	}
}
// Reset the nodeAr's Topic Index - last topic in array,
void CNodeArray::UpdateTopicIndex(int index)
{
	int nNodes = GetSize();
	if(nNodes)
	{
		CNodeItem item = GetAt(nNodes - 1);
		item.m_arIndex = index;
		SetAt(nNodes - 1, item); 
	}
}

void CNodeArray::Read(CArchive & ar, CTopicStrAliasAr & aliasAr, int & version)
{
	int sz;
	ar >> sz;
	if(sz)
	{
		CNodeItem item;
		for(int i = 0; i < sz; i ++)
		{
			item.Zero();
			item.Read(ar, aliasAr, version);
			Add(item);
		}
	}
}
void CNodeArray::Write(CArchive & ar, CTopicStrAliasAr & aliasAr, int & version)
{
	int sz = GetSize();
	ar << sz;
	for(int i = 0; i < sz; i++)
	{
		GetAt(i).Write(ar, aliasAr, version);
	}
}

/////////////////////////////////////////////////////////////////////////////
// CExtension 12/22-5
CExtension::CExtension()
{
	Zero();
}
CExtension::~CExtension()
{
}
CExtension::CExtension(CString & strExtension, MEDIA_TYPE & type)
{
	m_strExtension = strExtension;
	m_extType = type;
}
void CExtension::operator=(const CExtension & extension)
{
	CopyExtension(extension);
}
BOOL CExtension::operator==(const CExtension & extension)
{
	if(m_strExtension == extension.m_strExtension &&
	   m_extType == extension.m_extType)
	   return TRUE;
	return FALSE;

}
BOOL CExtension::operator!=(const CExtension & extension)
{
	return !(*this == extension); 
}
void CExtension::Zero()
{
	m_strExtension = _T("");
	m_extType = NONE;

}
void CExtension::CopyExtension(const CExtension & extension)
{
	m_strExtension = extension.m_strExtension;
	m_extType = extension.m_extType;
}

/////////////////////////////////////////////////////////////////////
// CExtensionArray.  
CExtensionArray::CExtensionArray()
{
}
CExtensionArray::~CExtensionArray()
{
	RemoveAll();
}

// Get extension data from url or file path.
BOOL CExtensionArray::GetPathExtension(CString path, CExtension & extension)
{
	// Lowercase.
	path.MakeLower();

	// Prep param.
	extension.Zero();

	BOOL ret = FALSE;
	CString extStr;

	// Path a URL.
	BOOL bIsUrl = FALSE;
	if(path.Find(_T("http"), 0) != -1 )
		bIsUrl = TRUE;

	// Get extension.
	int len = path.GetLength();
	int pos = path.ReverseFind(_T('.'));
	if(pos != -1)
	{
		int nChars = len - pos - 1;
		if(nChars > 0 && nChars < 5)	// mp4, m3u8
		{
			extStr = path.Right(len - pos - 1);
			extStr.MakeLower();
			if(IsValidExtension(extStr))
			{
				extension.m_extType = GetExtensionMediaType(extStr, bIsUrl);
				extension.m_strExtension = extStr;
				return TRUE;
			}
			// else ret == FALSE
		}
		// else ret == FALSE
	}
	// Else no extension - probably a video or audio stream.
	//  For example: http://5.254.76.34:17070/C441/index.m3u8?token=kdsdkwy453wrRq29IIIo
	else if(bIsUrl)
	{
		// Video - try common extensions embedded in url.
		if(path.Find(_T("mp4"),  0) != -1 ||
		   path.Find(_T("m3u8"), 0) != -1 ||
		   path.Find(_T("hls"),  0) != -1 )
			extension.m_extType = VIDU;
		// Else audio - like radio stream.
		else
			extension.m_extType = AUDU;
		
		// No extension for stream.
		extension.m_strExtension = _T("");
		return TRUE;
	}
	return FALSE;
}


// Check if extension is in the list.
BOOL CExtensionArray::IsValidExtension(CString extStr)
{
	// Lowercase.
	extStr.MakeLower();

	CExtension ext;
	int sz = GetSize();
	for(int i = 0; i < sz; i++)
	{
		ext = GetAt(i);
		if(ext.m_strExtension == extStr)
			return TRUE;
	}
	return FALSE;
}

// Get media type for extension.
MEDIA_TYPE CExtensionArray::GetExtensionMediaType(CString strExtension, BOOL bIsUrl)
{
	// Lowercase.
	strExtension.MakeLower();

	CExtension ext;
	int sz = GetSize();
	for(int i = 0; i < sz; i++)
	{
		ext = GetAt(i);
		if(ext.m_strExtension == strExtension)
		{
			if(ext.m_extType == AUDF && bIsUrl)			// audio stream
				return AUDU;
			else if(ext.m_extType == VIDF && bIsUrl)	// video stream
				return VIDU;
			else
				return ext.m_extType;					// audio or video file
		}
	}
	return NONE;
}

// Adds an extension to the list - using CString constructor.
void CExtensionArray::AddExtension(CString strExtension, MEDIA_TYPE type)
{
	CExtension extension(strExtension, type);
	Add(extension);
}

// Call once at start up.
void CExtensionArray::BuildExtensions()
{
	// These extensions all have the type set as either
	// AUDF or VIDF for files.
	// If the media item as a url, they would be 
	// AUDU or VIDU for internet URL streams. See GetExtensionMediaType().
	// TODO:
	//  Guessing if audio or video for some - containers can be any.
	//  List incomplete.
	//  From somewhere on the net. See FFMPEG_extensions.txt.
	RemoveAll();
	
	AddExtension(_T("3g2"), VIDF);	
	AddExtension(_T("3gp"), VIDF);
	AddExtension(_T("3gp2"), VIDF);
	AddExtension(_T("asf"), VIDF);
	AddExtension(_T("avi"), VIDF);
	AddExtension(_T("bdmv"), VIDF);	// Blu-Ray
	AddExtension(_T("bup"), VIDF);	// DVD
	AddExtension(_T("cda"), VIDF);	// CD Audio
	AddExtension(_T("exr"), AUDF);
	AddExtension(_T("flv"), VIDF);
	AddExtension(_T("gxf"), VIDF);
	AddExtension(_T("ifv"), VIDF);
	AddExtension(_T("ifo"), VIDF);  // DVD
	AddExtension(_T("kux"), VIDF);
	AddExtension(_T("m2t"), VIDF);
	AddExtension(_T("m2ts"), VIDF);
	AddExtension(_T("m4v"), VIDF);
	AddExtension(_T("mkv"), VIDF);
	AddExtension(_T("mod"), VIDF);
	AddExtension(_T("mov"), VIDF);
	AddExtension(_T("mp2"), AUDF);	// DVD - Jacket folder, and...
	AddExtension(_T("mp3"), AUDF);	
	AddExtension(_T("mp4"), VIDF);	
	AddExtension(_T("mpg"), VIDF);
	AddExtension(_T("mpls"), VIDF);	// Blu-Ray
	AddExtension(_T("mxf"), VIDF);
	AddExtension(_T("m3u8"), VIDF);	// IPTV
	AddExtension(_T("tod"), VIDF);
	AddExtension(_T("vob"), VIDF);	// DVD
	AddExtension(_T("webm"), VIDF);
	AddExtension(_T("wma"), AUDF);
	AddExtension(_T("wmv"), VIDF);
	AddExtension(_T("xmv"), VIDF);

/*	AUDU,	// audio url	- http, https
	VIDU,	// video url	- http, https
	VIDF,	// video file	- mp4,
	AUDF 	// audio file	- mp3,*/
}








