#include "CDTreeNodes.h"

#include <string.h>

#include "WimpLib:DocView.h"
#include "WimpLib:Desktop.h"
#include "WimpLib:Display.h"
#include "WimpLib:Exception.h"
#include "WimpLib:Keyboard.h"
#include "WimpLib:mem.h"
#include "WimpLib:Message.h"
#include "WimpLib:OTreeList.h"
#include "WimpLib:Task.h"
#include "WimpLib:Utils.h"

#include "DigitalCD.h"
#include "CDTreeView.h"
#include "Options.h"

// Order of enum must follow sorting order
// bottom 4 bits define object type, the rest, sorting order
typedef enum
// - Tracks -
{ Entity_Collective          = 0x09
, Entity_Artist              = 0x0A
, Entity_Box                 = 0x0B
, Entity_Album               = 0x0C
, Entity_Track               = 0x0D
} Entity;

typedef enum
{ Key_Unknown             = 0x00
// - Tracks -
, Key_NC                  = 0x10
, Key_Collective          = 0x00 | Entity_Collective
, Key_HComp_Collective    = 0x20 | Entity_Collective
, Key_Artist              = 0x00 | Entity_Artist
, Key_HComp_Artist        = 0x20 | Entity_Artist
, Key_Box                 = 0x00 | Entity_Box
, Key_Comp_Box            = 0x20 | Entity_Box
, Key_Album               = 0x00 | Entity_Album
, Key_Comp_Album          = 0x20 | Entity_Album
, Key_Track               = 0x90 | Entity_Track
, Key_FlatTrack           = 0xF0 | Entity_Track
, Key_Mask_Entity         = 0x0F
, Key_Mask_Sort           = 0x1F
} Key;

typedef struct ChildInfo ChildInfo;

static void Track_GetSubEntity(const CDTrack* pTrack, ChildInfo* pInfo);

typedef struct
{
	uint32_t            Key;
} KeyInfo;

typedef struct
{
	uint32_t            Key;
	const CDTrack*      pTrack;
} TrackInfo;

// AlbumInfo will be prefilled in Child info, updated has we follow the grouping chain
// and BoxInfo & ArtistInfo will be extracted from it so it must follow the same layout.
// !!! Attention:
// NULL and &nil have some different meaning, in these structure
// so beware that StrCol_Add(NULL) returns &nil.
typedef struct
{
	uint32_t            Key;
	const char*         Collective;
	const char*         Artist;
	const char*         Box;
	const char*         Date;
	const char*         Album;
	unsigned long       AlbumId;
	const CDDesc*       pDesc;
} AlbumInfo;

typedef struct
{
	uint32_t            Key;
	const char*         Collective;
	const char*         Artist;
	const char*         Box;
	const char*         Date;
} BoxInfo;

typedef struct
{
	uint32_t            Key;
	const char*         Collective;
	const char*         Artist;
} ArtistInfo;

typedef struct
{
	uint32_t            Key;
	const char*         Collective;
} CollectiveInfo;

struct ChildInfo
{
	const OTreeNodeVPtr* pVPtr;
	uint32_t            size;
	uint32_t            flags;
	union
	{
		KeyInfo         key;
		TrackInfo       track;
		AlbumInfo       album;
		BoxInfo         box;
		ArtistInfo      artist;
		CollectiveInfo  collective;
	} i;
};

/*-------------------------------------------------------------------------*
 *--- Global functions ----------------------------------------------------*
 *-------------------------------------------------------------------------*/

static struct
{
	int   Tree_Indent;
	CRect Text;
	struct
	{
		CRect  o;
		CSize  size;
	} Group;
	struct
	{
		CRect  o;
	} Clue; // Also used for Marks
	struct
	{
		CRect  o;
		CSize  size;
	} Object;
	struct
	{
		CRect  o;
		CSize  size;
	} VSlider;
} Sizes;

void throw_CDTreeNodes_CDTreeNodes(void)
{
	// Read the various object sizes
	Sizes.Tree_Indent = 40;
	Sizes.Text.y1 = 40;
	Sizes.Group.size   = Desktop_GetNamedSpriteSize("n_files");
	Sizes.Object.size  = Desktop_GetNamedSpriteSize("i_file");
	Sizes.VSlider.size = Desktop_GetNamedSpriteSize("vslider");

	if (Sizes.Text.y1 < Sizes.Group.size.cy + 4)
		Sizes.Text.y1 = Sizes.Group.size.cy + 4;
	if (Sizes.Text.y1 < Sizes.Object.size.cy + 4)
		Sizes.Text.y1 = Sizes.Object.size.cy + 4;
	if (Sizes.Text.y1 < Sizes.VSlider.size.cy + 4)
		Sizes.Text.y1 = Sizes.VSlider.size.cy + 4;
	if (Sizes.Text.y1 & 3)
	{
		Sizes.Text.y1 &= ~3;
		Sizes.Text.y1 += 4;
	}
	Sizes.Text.x0 = 8;
	Sizes.Text.x1 = 16;
	Sizes.Text.y0 = (Sizes.Text.y1 - 40) >> 1;

	Sizes.Clue.o.x0 = 8;
	Sizes.Clue.o.y0 = Sizes.Text.y0 + 4;
	Sizes.Clue.o.x1 = 40;
	Sizes.Clue.o.y1 = Sizes.Text.y1;

	Sizes.Group.o.x0 = 4;
	Sizes.Group.o.x1 = 8 + Sizes.Group.size.cx;
	Sizes.Group.o.y0 = ((Sizes.Text.y1 - Sizes.Group.size.cy + 2) >> 1) & ~1;
	Sizes.Group.o.y1 = Sizes.Text.y1;
	Sizes.Object.o.x0 = 4;
	Sizes.Object.o.x1 = 8 + Sizes.Object.size.cx;
	Sizes.Object.o.y0 = ((Sizes.Text.y1 - Sizes.Object.size.cy + 2) >> 1) & ~1;
	Sizes.Object.o.y1 = Sizes.Text.y1;
	Sizes.VSlider.o.x0 = 0;
	Sizes.VSlider.o.x1 = Sizes.VSlider.size.cx;
	Sizes.VSlider.o.y0 = ((Sizes.Text.y1 - Sizes.VSlider.size.cy + 2) >> 1) & ~1;
	Sizes.VSlider.o.y1 = Sizes.Text.y1;
}

void CDTreeNodes_NotCDTreeNodes(void)
{
}

int CDHeaderNode_GetFirstColumnPos(void)
{
	return Sizes.Clue.o.x1
		 + Sizes.Object.o.x1;
}

/*-------------------------------------------------------------------------*
 *--- Compare functions to be used by a tree node when inserting childs. --*
 *-------------------------------------------------------------------------*/

static int Compare_Strings(const char* pa, const char* pb)
{
	if (pa)
	{
		if (pb)
		{
			if (*pa)
			{
				if (*pb)
				{
					// First compare without taking case and accents into account
					int val = String_Collate(3, pa, pb, -1);

					// If equal, distinguish case and accents
					if (!val) val = String_Collate(0, pa, pb, -1);

					return val;
				}

				return 1;
			}
			else if (*pb)
				return -1;

			return 0;
		}

		return 1;
	}
	else if (pb)
		return -1;

	return 0;
}

static int Compare_RStrings(const char* pa, const char* pb)
{
	if (pa)
	{
		if (pb)
		{
			if (*pa)
			{
				if (*pb)
				{
					// First compare without taking case and accents into account
					int val = String_Collate(3, pa, pb, -1);

					// If equal, distinguish case and accents
					if (!val) val = String_Collate(0, pa, pb, -1);

					return val;
				}

				return -1;
			}
			else if (*pb)
				return 1;

			return 0;
		}

		return 1;
	}
	else if (pb)
		return -1;

	return 0;
}

static int Compare_Tracks(const TrackInfo* pa, const TrackInfo* pb)
{
	int val = 0;

	switch(pa->Key & CDTreeMode_Mask_OrderBy)
	{
		case CDTreeMode_OrderBy_Alphabetic:
		{
			val = Compare_Strings(CDTrack_GetMetaString(pa->pTrack, EMetaId_StreamTitle)
								, CDTrack_GetMetaString(pb->pTrack, EMetaId_StreamTitle));
		}
		// no break;
		default:
		{
			if (!val) val = CDTrack_GetOrder(pa->pTrack) - CDTrack_GetOrder(pb->pTrack);
		}
		break;
	}

	// No childs possible, must somehow distinguish them
	if (!val)
		val = ((char*) pa->pTrack) - ((char*) pb->pTrack);

	return val;
}

static int Compare_SameChilds(const void* pa, const void* pb)
{
	const KeyInfo* sa = pa;
	int val = 0;

    // Compare objects of same type
	switch(sa->Key & Key_Mask_Entity)
	{
		case Entity_Collective:
		{
			const CollectiveInfo* sa = pa;
			const CollectiveInfo* sb = pb;

			val = Compare_RStrings(sa->Collective, sb->Collective);
		}
		break;
		case Entity_Artist:
		{
			const ArtistInfo* sa = pa;
			const ArtistInfo* sb = pb;

			val = Compare_RStrings(sa->Artist, sb->Artist);
			if (!val) val = Compare_RStrings(sa->Collective, sb->Collective);
		}
		break;
		case Entity_Box:
		{
			const BoxInfo* sa = pa;
			const BoxInfo* sb = pb;

			if (sa->Key & CDTreeMode_SortAlbum_Date)
				val = Compare_RStrings(sa->Date, sb->Date);

			if (!val) val = Compare_RStrings(sa->Box, sb->Box);
			if (!val) val = Compare_RStrings(sa->Collective, sb->Collective);
			if (!val) val = Compare_RStrings(sa->Artist, sb->Artist);
			if (!val) val = Compare_RStrings(sa->Date, sb->Date);
		}
		break;
		case Entity_Album:
		{
			const AlbumInfo* sa = pa;
			const AlbumInfo* sb = pb;

			if (sa->Key & CDTreeMode_SortAlbum_Date)
				val = Compare_RStrings(sa->Date, sb->Date);

			if (!val) val = Compare_RStrings(sa->Album, sb->Album);
			if (!val) val = Compare_RStrings(sa->Collective, sb->Collective);
			if (!val) val = Compare_RStrings(sa->Artist, sb->Artist);
			if (!val) val = Compare_RStrings(sa->Box, sb->Box);
			if (!val) val = Compare_RStrings(sa->Date, sb->Date);
			if (!val)
			{
				if (sa->AlbumId > sb->AlbumId)
					val = 1;
				else if (sa->AlbumId < sb->AlbumId)
					val = -1;
			}
		}
		break;
		case Entity_Track:
		{
			val = Compare_Tracks(pa, pb);
		}
		break;
	}

	return val;
}

static int Compare_Flat(const TrackInfo* pa, const TrackInfo* pb)
{
	ChildInfo InfoA;
	ChildInfo InfoB;
	int val;

	InfoA.i.key.Key = InfoB.i.key.Key = Key_Unknown | (pa->Key & ~CDTreeMode_Mask_Keys);

	while(1)
	{
		Track_GetSubEntity(pa->pTrack, &InfoA);
		Track_GetSubEntity(pb->pTrack, &InfoB);

		// entity compare
		val = (InfoA.i.key.Key & Key_Mask_Sort) - (InfoB.i.key.Key & Key_Mask_Sort);
		if (val) return val;

		// They belong to the same kind of entity
		val = Compare_SameChilds(&InfoA.i, &InfoB.i);
		if (val) return val;

		if ((InfoA.i.key.Key & CDTreeMode_Mask_Keys) >= Key_Track)
			break;
	}

	// No childs possible, must somehow distinguish them
	val = ((char*) pa->pTrack) - ((char*) pb->pTrack);

	return val;
}

static int Compare_Childs(const void* pa, const void* pb)
{
	const KeyInfo* sa = pa;
	const KeyInfo* sb = pb;

	// Compare by object types
	int val = (sa->Key & Key_Mask_Sort) - (sb->Key & Key_Mask_Sort);
	if (val) return val;

	if ((sa->Key & CDTreeMode_Mask_Keys) == Key_FlatTrack)
		return Compare_Flat(pa, pb);

	return Compare_SameChilds(pa, pb);
}

//--------------------------------------------------------------------------

static void Node_GetTextColors(const WPlotItem* pItem, int* bcol, int* fcol)
{
	switch (pItem->m_flags & (EWItem_Active | EWItem_Selected))
	{
		case EWItem_Active:
		{
			*bcol = 1;
			*fcol = 4;
		}
		break;
		case EWItem_Selected:
		{
			*bcol = 7;
			*fcol = 0;
		}
		break;
		case (EWItem_Active + EWItem_Selected):
		{
			*bcol = 5;
			*fcol = 0;
		}
		break;
		default:
		{
			*bcol = 0;
			*fcol = 7;
		}
	}
}

static int GetWidth_NodeSpriteString(const OTreeNode* pNode, const char* pString)
{
	return OTreeNode_DefWidth(pNode)
	       + Sizes.Group.o.x1 + Sizes.Text.x1
	       + Desktop_GetTextWidth(pString);
}

static int GetWidth_CDNodeString(const OTreeNode* pNode, const char* pString)
{
	return OTreeNode_DefWidth(pNode)
	       + Sizes.Object.o.x1
	       + Sizes.Text.x1 + Desktop_GetTextWidth(pString);
}

static void Plot_Grid(const OTreeNode* pNode, const WPlotItem* pItem)
{
	const OTreeNode* pTmp;
	const OTreeNode* pBrother;
	CRect  box;
	int    i, x, y;
	bool bHasBox = OTreeNode_AllowExpand(pNode);
	int level = OTreeNode_GetLevel(pNode);
	WListCore* pList = OTreeList_GetWListCore(OTreeNode_GetTreeList(pNode));

	box = RectToScreen(&pItem->m_box, &pItem->m_cvtinfo);
	RoundRectForPlot(&box);

	x = box.x0 + (level * Sizes.Tree_Indent) - Sizes.Tree_Indent/2;
	y = (box.y1 + box.y0)/2;

	// Set dash-line pattern
	Display_SetDashPattern(8, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa);

	if (level > 1)
	{
		Display_Plot(4, x, box.y1);
		Display_Plot(29, x, y + (bHasBox ? 8 : 0));
	}

	pBrother = OTreeNode_GetSuccessor(pNode);
	if (pBrother && (level > 1))
	{
		Display_Plot(4, x, y - (bHasBox ? 8 : 0));
		Display_Plot(29, x, box.y0);
	}

	if ((level > 1) || bHasBox)
	{
		Display_Plot(4, x + (bHasBox ? 8 : 0), y);
		Display_Plot(29, box.x0 - 1 + level * Sizes.Tree_Indent, y);
	}

	if (bHasBox)
	{
		Display_Plot(4, x - 8, y - 8);
		Display_Plot(5, x - 8, y + 8);
		Display_Plot(5, x + 8, y + 8);
		Display_Plot(5, x + 8, y - 8);
		Display_Plot(5, x - 8, y - 8);
		if ((pItem->m_index + 1) < WListCore_Count(pList))
		{
			pTmp = WListCore_Get(pList, pItem->m_index + 1);
			pTmp = OTreeNode_GetParent(pTmp);
		}
		else
			pTmp = NULL;

		if (pTmp != pNode)
		{
			Display_Plot(4, x, y - 4);
			Display_Plot(5, x, y + 4);
		}
		Display_Plot(4, x - 4, y);
		Display_Plot(5, x + 4, y);
	}

	pTmp = OTreeNode_GetParent(pNode);

	for (i = level - 2; i >= 1; i--)
	{
		x -= Sizes.Tree_Indent;
		pBrother = OTreeNode_GetSuccessor(pTmp);
		if (pBrother)
		{
			Display_Plot(4, x, box.y1);
			Display_Plot(29, x, box.y0);
		}
		pTmp = OTreeNode_GetParent(pTmp);
	}
}

static void Plot_Base(const OTreeNode* pNode, const WPlotItem* pItem, CRect* pbox, const CDTrack* pTrack)
{
	int width = OTreeNode_DefWidth(pNode);
	*pbox = RectToScreen(&pItem->m_box, &pItem->m_cvtinfo);

	RoundRectForPlot(pbox);

	// Plot Grid
	Desktop_SetStdColour(7);
	Plot_Grid(pNode, pItem);

	if (pTrack)
	{
		CPoint pt;

		// Plot marks
		pt.x = pbox->x0 + Sizes.Clue.o.x0;
		pt.y = pbox->y0 + Sizes.Clue.o.y0;
		if (pItem->m_flags & EWItem_Marked)
			Desktop_PlotNamedSprite("m_found", pt, NULL);
	}

	pbox->x0 += width;
}

static void Plot_CDObjectInfo(const WPlotItem* pItem, CRect* pbox, const CDTrack* pTrack)
{
	const char* pSpriteName = NULL;
	CPoint      pt;

	IGNORE(pItem);
	IGNORE(pTrack);

	pSpriteName = "i_file";

	if (pSpriteName)
	{
		pt.x = pbox->x0 + Sizes.Object.o.x0;
		pt.y = pbox->y0 + Sizes.Object.o.y0;
		Desktop_PlotNamedSprite(pSpriteName, pt, NULL);
	}

	pbox->x0 += Sizes.Object.o.x1;
}

static void Area_CDObjectInfo(const Mouse* pMouse, const CDTrack* pTrack, WItemArea* pArea)
{
	IGNORE(pTrack);

	int x1 = pArea->rect.x0 + Sizes.Object.o.x1;

	if (pMouse == NULL)
	{
		pArea->rect.x0 = x1;
		return;
	}

	if (pMouse->pt.x < x1)
	{
		pArea->id = EWItemArea_Inactive;
		pArea->rect.x1 = x1;
		return;
	}

	pArea->rect.x0 = x1;
}

static void Plot_TextBack(const WPlotItem* pItem, const CRect* pbox, int type, int bcol, int fcol)
{
	int ptype = -1;

	if (bcol != 0)
	{
		Desktop_SetStdColour(bcol);
		Display_Plot(4, pbox->x0, pbox->y0);
		Display_Plot(101, pbox->x1, pbox->y1);
	}

	Desktop_SetStdColour(fcol);

	// Determine what to plot
	switch(type)
	{
		case 0:
		{
			if (pItem->m_flags & EWItem_HasFocus)
				ptype = 0;
		}
		break;
		case 1:
		{
			if (pItem->m_flags & EWItem_HasFocus)
				ptype = 0;
			else if (!(pItem->m_flags & (EWItem_Active | EWItem_Selected)))
				ptype = 1;
		}
		break;
		case 2:
		{
			if (pItem->m_flags & EWItem_HasFocus)
				ptype = 1;
		}
		break;
	}

	// Plot
	switch(ptype)
	{
		case 0:
		{
			// Set dash-line pattern
			Display_SetDashPattern(24, 0xdb, 0x6d, 0xb6, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc);
			// plot dashed rectangle
			Display_Plot(4, pbox->x0, pbox->y0);
			Display_Plot(21, pbox->x0, pbox->y1);
			Display_Plot(53, pbox->x1, pbox->y1);
			Display_Plot(53, pbox->x1, pbox->y0);
			Display_Plot(53, pbox->x0, pbox->y0);
		}
		break;
		case 1:
		{
			// Plot filled rectangle as underline
			Display_Plot(4, pbox->x0 + 8, pbox->y0 + 4);
			Display_Plot(101, pbox->x1 - 8, pbox->y0 + 7);
		}
		break;
	}
}

static void Plot_Text(const WPlotItem* pItem, CRect* pbox, const char* pString, int type)
{
	CRect box = *pbox;
	int bcol, fcol;
	int width;

	Node_GetTextColors(pItem, &bcol, &fcol);

	width = Desktop_GetTextWidth(pString) + Sizes.Text.x1;

	// Plot filled rectangle
	box.x1 = box.x0 + width - Task_GetModeInfo()->dx;

	Plot_TextBack(pItem, &box, type, bcol, fcol);

	// Plot text
	Desktop_SetStdColour(fcol);
	Desktop_SetStdColour(0x80 + bcol);
	box.x1 = box.x0 + width - Sizes.Text.x0;
	box.x0 += Sizes.Text.x0;
	Desktop_PlotText(pString, &box);

	pbox->x0 += width;
}

static void Plot_SpriteString(const OTreeNode* pNode, const WPlotItem* pItem, const char* pSprite, const char* pString)
{
	CRect box;
	CPoint pt;
	int bcol, fcol;

	Node_GetTextColors(pItem, &bcol, &fcol);

	Plot_Base(pNode, pItem, &box, NULL);

	// Plot Sprite
	pt.x = box.x0 + Sizes.Group.o.x0;
	pt.y = box.y0 + Sizes.Group.o.y0;
	Desktop_PlotNamedSprite(pSprite, pt, NULL);
	box.x0 += Sizes.Group.o.x1;

	// Plot Text
	Plot_Text(pItem, &box, pString, 1);
}

static CSize Node_GetBox(const void* pObject, const void* pOwner, const int* pSizes, const int* pMaxSizes)
{
	CSize size = {pMaxSizes[0], Sizes.Text.y1};

	IGNORE(pObject);
	IGNORE(pOwner);
	IGNORE(pSizes);

	return size;
}

static void Node_GetArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	const OTreeNode* pNode = pObject;

	IGNORE(pOwner);

	OTreeNode_DefArea(pNode, pItem, pMouse, pArea);

	if (pMouse == NULL)
	{
		pArea->rect.x1 = pItem->m_box.x0 + pItem->m_psizes[0];
	}
	else
	{
		if (pArea->id != EWItemArea_Select)
			return;

		if (pMouse->pt.x > (pItem->m_box.x0 + pItem->m_psizes[0]))
			pArea->id = EWItemArea_Outside;
	}
}

static void Node_GetSpriteStringArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	const OTreeNode* pNode = pObject;
	int x1;

	IGNORE(pOwner);

	OTreeNode_DefArea(pNode, pItem, pMouse, pArea);

	if (pMouse == NULL)
	{
		pArea->rect.x0 = pArea->rect.x0 + Sizes.Group.o.x1;
		pArea->rect.x1 = pItem->m_box.x0 + pItem->m_psizes[0];
	}
	else
	{
		if (pArea->id != EWItemArea_Select)
			return;

		if (pMouse->pt.x > (pItem->m_box.x0 + pItem->m_psizes[0]))
		{
			pArea->id = EWItemArea_Outside;
			return;
		}

		x1 = pArea->rect.x0 + Sizes.Group.o.x1;
		if (pMouse->pt.x < x1)
		{
			pArea->id = EWItemArea_Outside;
			pArea->rect.x1 = x1;
			return;
		}

		pArea->rect.x0 = x1;
	}
}

static void Node_CDGetArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	const OTreeNode* pNode = pObject;
	const TrackInfo* info = OTreeNode_GetData(pNode);
	const CDTrack* pTrack = info->pTrack;

	IGNORE(pOwner);

	OTreeNode_DefArea(pNode, pItem, pMouse, pArea);

	if (pMouse == NULL)
	{
		Area_CDObjectInfo(pMouse, pTrack, pArea);
		pArea->rect.x1 = pItem->m_box.x0 + pItem->m_psizes[0];
	}
	else
	{
		if (pArea->id != EWItemArea_Select)
			return;

		if (pMouse->pt.x > (pItem->m_box.x0 + pItem->m_psizes[0]))
		{
			pArea->id = EWItemArea_Outside;
			return;
		}

		Area_CDObjectInfo(pMouse, pTrack, pArea);
	}
}

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static const char* NodeTrack_String(const TrackInfo* pInfo)
{
	const CDTrack* pTrack = pInfo->pTrack;
	unsigned int order = CDTrack_GetOrder(pTrack);
	const char* pName = CDTrack_GetLabel(pTrack);

	if (((pInfo->Key & CDTreeMode_Mask_OrderBy) == CDTreeMode_OrderBy_TrackNumber)
	&&  (order > 0))
	{
		return SPrintf("%02d - %s", order, pName);
	}

	return pName;
}

static const char* NodeFlatTrack_String(const TrackInfo* pInfo, int index)
{
	const CDTrack* pTrack = pInfo->pTrack;
	const CDDesc* pDesc = CDTrack_GetCDDesc(pTrack);
	const char* pString;
	const char* pString2;
	const char* pString3;

	switch(index)
	{
		case ECDField_Title:
		{
			pString = NodeTrack_String(pInfo);
		}
		break;
		case ECDField_Author:
		{
			pString = CDTrack_GetMetaString(pTrack, EMetaId_StreamArtist);
			pString2 = CDTrack_GetMetaString(pTrack, EMetaId_StreamCollective);

			if (*pString2)
			{
				if (*pString)
					pString = SPrintf("%s - %s", pString2, pString);
				else
					pString = pString2;
			}
		}
		break;
		case ECDField_Album:
		{
			pString = CDDesc_GetLabel(pDesc);
			pString2 = CDDesc_GetMetaString(pDesc, EMetaId_StreamBox);
			pString3 = CDDesc_GetMetaString(pDesc, EMetaId_StreamDate);

			if (*pString3)
			{
				if (pInfo->Key & CDTreeMode_SortAlbum_Date)
				{
					if (*pString2)
					{
						if (*pString)
							pString = SPrintf("[%s] %s - %s", pString3, pString2, pString);
						else
							pString = SPrintf("[%s] %s", pString3, pString2);
					}
					else if (*pString)
						pString = SPrintf("[%s] %s", pString3, pString);
					else
						pString = SPrintf("[%s]", pString3);
				}
				else
				{
					if (*pString2)
					{
						if (*pString)
							pString = SPrintf("%s - %s [%s]", pString2, pString, pString3);
						else
							pString = SPrintf("%s [%s]", pString2, pString3);
					}
					else if (*pString)
						pString = SPrintf("%s [%s]", pString, pString3);
					else
						pString = SPrintf("[%s]", pString3);
				}
			}
			else
			{
				if (*pString2)
				{
					if (*pString)
						pString = SPrintf("%s - %s", pString2, pString);
					else
						pString = pString2;
				}
			}
		}
		break;
		case ECDField_AlbumAuthor:
		{
			pString = CDDesc_GetMetaString(pDesc, EMetaId_StreamArtist);
			pString2 = CDDesc_GetMetaString(pDesc, EMetaId_StreamCollective);

			if (*pString2)
			{
				if (*pString)
					pString = SPrintf("%s - %s", pString2, pString);
				else
					pString = pString2;
			}
		}
		break;
		default:
			pString = &nil;
	}

	return pString;
}

static void NodeFlatTrack_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const CDTreeView* pView = pOwner;
	const CDField_Info* pFieldList;
	const OTreeNode* pNode = pObject;
	const TrackInfo* info = OTreeNode_GetData(pNode);
	const CDTrack* pTrack = info->pTrack;
	const char* pString;
	CRect box, refbox;
	CPoint pt;
	int bcol, fcol;
	int count, i;
	int width;

	box = RectToScreen(&pItem->m_box, &pItem->m_cvtinfo);
	RoundRectForPlot(&box);

	Desktop_SetStdColour(7);

	// Plot marks
	pt.x = box.x0 + Sizes.Clue.o.x0;
	pt.y = box.y0 + Sizes.Clue.o.y0;
	if (pItem->m_flags & EWItem_Marked)
		Desktop_PlotNamedSprite("m_found", pt, NULL);

	box.x0 += Sizes.Clue.o.x1;

	Plot_CDObjectInfo(pItem, &box, pTrack);

	Node_GetTextColors(pItem, &bcol, &fcol);

	// Plot filled rectangle
	box.x1 -= Task_GetModeInfo()->dx;
	Plot_TextBack(pItem, &box, 0, bcol, fcol);

	// Plot texts
	Desktop_SetStdColour(fcol);
	Desktop_SetStdColour(0x80 + bcol);
    refbox = box;

	count = CDTreeView_GetFieldList(pView, &pFieldList);

	for (i = 0; i < count; i++)
	{
		// Plot text
		width = pFieldList[i].width;
		pString = NodeFlatTrack_String(info, pFieldList[i].id);
		box.x1 = refbox.x0 + width - Sizes.Text.x0;
		box.x0 = refbox.x0 + Sizes.Text.x0;
		Desktop_PlotText(pString, &box);
		// Move to next position
		refbox.x0 += width;
	}
}

static void NodeFlatTrack_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	IGNORE(pObject);
	IGNORE(pOwner);
	IGNORE(psizes);
}

static CSize NodeFlatTrack_GetBox(const void* pObject, const void* pOwner, const int* pSizes, const int* pMaxSizes)
{
	const CDTreeView* pView = pOwner;
	const CDField_Info* pFieldList;
	int count, i;
	CSize size = { Sizes.Clue.o.x1
	             + Sizes.Object.o.x1
	             , Sizes.Text.y1
	             };

	IGNORE(pObject);
	IGNORE(pSizes);
	IGNORE(pMaxSizes);

	count = CDTreeView_GetFieldList(pView, &pFieldList);

	for (i = 0; i < count; i++)
		size.cx += pFieldList[i].width;
	return size;
}

static void NodeFlatTrack_GetArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	const OTreeNode* pNode = pObject;
	const CDTreeView* pView = pOwner;
	const TrackInfo* info = OTreeNode_GetData(pNode);
	const CDTrack* pTrack = info->pTrack;
	int selx0, x1;

	if (pMouse == NULL)
	{
		pArea->rect = pItem->m_box;
		pArea->rect.x0 = pArea->rect.x0 + Sizes.Clue.o.x1;
		Area_CDObjectInfo(pMouse, pTrack, pArea);
		selx0 = pArea->rect.x0;

		const CDField_Info* pFieldList;
		int count = CDTreeView_GetFieldList(pView, &pFieldList);

		for (int i = 0; i < count; i++)
		{
			x1 = pArea->rect.x0 + pFieldList[i].width;
			if (pArea->id == CDTreeView_GetFieldAction(pTrack, pFieldList[i].id))
			{
				if (pArea->id == EWItemArea_Select)
					pArea->rect.x0 = selx0;
				else
					pArea->rect.x1 = x1;
				return;
			}
			pArea->rect.x0 = x1;
		}

		pArea->rect.x0 = selx0;
	}
	else
	{
		pArea->rect = pItem->m_box;
		pArea->id = EWItemArea_Select;

		x1 = pArea->rect.x0 + Sizes.Clue.o.x1;
		if (pMouse->pt.x < x1)
		{
			pArea->id = EWItemArea_Outside;
			return;
		}
		pArea->rect.x0 = x1;

		Area_CDObjectInfo(pMouse, pTrack, pArea);
		if (pArea->id != EWItemArea_Select)
			return;

		if (!Keyboard_PollAlt())
			return;

		selx0 = pArea->rect.x0;

		const CDField_Info* pFieldList;
		int count = CDTreeView_GetFieldList(pView, &pFieldList);

		for (int i = 0; i < count; i++)
		{
			x1 = pArea->rect.x0 + pFieldList[i].width;
			if (pMouse->pt.x < x1)
			{
				pArea->id = CDTreeView_GetFieldAction(pTrack, pFieldList[i].id);

				if (pArea->id == EWItemArea_Select)
					pArea->rect.x0 = selx0;
				else
					pArea->rect.x1 = x1;
				return;
			}
			pArea->rect.x0 = x1;
		}

		pArea->rect.x0 = selx0;
	}
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeFlatTrack_VPtr =
{ { NodeFlatTrack_Plot
  , NodeFlatTrack_GetSize
  , NodeFlatTrack_GetBox
  , NodeFlatTrack_GetArea
  }
, NULL
, NULL
, NULL
, NULL
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static void NodeTrack_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	const OTreeNode* pNode = pObject;
	const TrackInfo* pInfo = OTreeNode_GetData(pNode);

	IGNORE(pOwner);

	psizes[0] = GetWidth_CDNodeString(pNode, NodeTrack_String(pInfo));
}

static void NodeTrack_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeNode* pNode = pObject;
	const TrackInfo* pInfo = OTreeNode_GetData(pNode);
	const CDTrack* pTrack = pInfo->pTrack;
	CRect box;

	IGNORE(pOwner);

	Plot_Base(pNode, pItem, &box, pTrack);
	Plot_CDObjectInfo(pItem, &box, pTrack);
	Plot_Text(pItem, &box, NodeTrack_String(pInfo), 0);
//	Plot_Void(pItem, &box);
}

static void NodeTrack_GetArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	Node_CDGetArea(pObject, pOwner, pItem, pMouse, pArea);

	if (pMouse == NULL)
		return;

	if ((pArea->id == EWItemArea_Select) && Keyboard_PollAlt())
	{
		pArea->id = CDItemAction_EditTitle;
		pArea->rect.x1 = pItem->m_box.x1;
	}
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeTrack_VPtr =
{ { NodeTrack_Plot
  , NodeTrack_GetSize
  , Node_GetBox
  , NodeTrack_GetArea
  }
, NULL
, NULL
, NULL
, NULL
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static void throw_NodeAlbum_AllocData(OTreeNode* This, void* pData, const void* pRefData)
{
	AlbumInfo* info = pData;
	const AlbumInfo* refinfo = pRefData;

	IGNORE(This);

	memset(info, 0, sizeof(*info));
	info->Key = refinfo->Key;
	info->pDesc = refinfo->pDesc;
	info->AlbumId = refinfo->AlbumId;
	info->Album = throw_StrCol_Add(DigitalCD.pAlbumsCol, refinfo->Album);
	if (refinfo->Box) info->Box = throw_StrCol_Add(DigitalCD.pBoxesCol, refinfo->Box);
	if (refinfo->Date) info->Date = throw_StrCol_Add(DigitalCD.pDatesCol, refinfo->Date);
	if (refinfo->Artist) info->Artist = throw_StrCol_Add(DigitalCD.pArtistsCol, refinfo->Artist);
	if (refinfo->Collective) info->Collective = throw_StrCol_Add(DigitalCD.pCollectivesCol, refinfo->Collective);
}

static void NodeAlbum_ClearData(OTreeNode* This, void* pData)
{
	AlbumInfo* info = pData;

	IGNORE(This);

	StrCol_Remove(DigitalCD.pAlbumsCol, info->Album);
	StrCol_Remove(DigitalCD.pBoxesCol, info->Box);
	StrCol_Remove(DigitalCD.pDatesCol, info->Date);
	StrCol_Remove(DigitalCD.pArtistsCol, info->Artist);
	StrCol_Remove(DigitalCD.pCollectivesCol, info->Collective);
}

static const char* NodeAlbum_String(const OTreeNode* pNode)
{
	const AlbumInfo* info = OTreeNode_GetData(pNode);

	if (info->Key & CDTreeMode_SortAlbum_Date)
	{
		if (info->Artist && *info->Artist)
		{
			if (info->Date && *info->Date)
				return SPrintf("[%s] %s (%s)", info->Date, info->Album, info->Artist);
			else
				return SPrintf("%s (%s)", info->Album, info->Artist);
		}
		else if (info->Date && *info->Date)
			return SPrintf("[%s] %s", info->Date, info->Album);
		else
			return info->Album;
	}
	else
	{
		if (info->Artist && *info->Artist)
		{
			if (info->Date && *info->Date)
				return SPrintf("%s [%s] (%s)", info->Album, info->Date, info->Artist);
			else
				return SPrintf("%s (%s)", info->Album, info->Artist);
		}
		else if (info->Date && *info->Date)
			return SPrintf("%s [%s]", info->Album, info->Date);
		else
			return info->Album;
	}
}

static void NodeAlbum_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	const OTreeNode* pNode = pObject;
	const char* pString = NodeAlbum_String(pNode);

	IGNORE(pOwner);

	psizes[0] = GetWidth_NodeSpriteString(pNode, pString);
}

static void NodeAlbum_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeNode* pNode = pObject;
	const AlbumInfo* info = OTreeNode_GetData(pNode);
	const char* pString = NodeAlbum_String(pNode);

	IGNORE(pOwner);

	if (info->Collective == NULL)
		Plot_SpriteString(pNode, pItem, "n_compil", pString);
	else
		Plot_SpriteString(pNode, pItem, "n_album", pString);
}

static void NodeAlbum_GetArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	Node_GetSpriteStringArea(pObject, pOwner, pItem, pMouse, pArea);

	if (pMouse == NULL)
		return;

	if ((pArea->id == EWItemArea_Select) && Keyboard_PollAlt())
	{
		pArea->id = CDItemAction_EditAlbum;
		pArea->rect.x1 = pItem->m_box.x1;
	}
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeAlbum_VPtr =
{ { NodeAlbum_Plot
  , NodeAlbum_GetSize
  , Node_GetBox
  , NodeAlbum_GetArea
  }
, throw_NodeAlbum_AllocData
, NodeAlbum_ClearData
, NULL
, Compare_Childs
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static void throw_NodeBox_AllocData(OTreeNode* This, void* pData, const void* pRefData)
{
	BoxInfo* info = pData;
	const BoxInfo* refinfo = pRefData;

	IGNORE(This);

	memset(info, 0, sizeof(*info));
	info->Key = refinfo->Key;
	info->Box = throw_StrCol_Add(DigitalCD.pBoxesCol, refinfo->Box);
	if (refinfo->Date) info->Date = throw_StrCol_Add(DigitalCD.pDatesCol, refinfo->Date);
	if (refinfo->Artist) info->Artist = throw_StrCol_Add(DigitalCD.pArtistsCol, refinfo->Artist);
	if (refinfo->Collective) info->Collective = throw_StrCol_Add(DigitalCD.pCollectivesCol, refinfo->Collective);
}

static void NodeBox_ClearData(OTreeNode* This, void* pData)
{
	BoxInfo* info = pData;

	IGNORE(This);

	StrCol_Remove(DigitalCD.pBoxesCol, info->Box);
	StrCol_Remove(DigitalCD.pDatesCol, info->Date);
	StrCol_Remove(DigitalCD.pArtistsCol, info->Artist);
	StrCol_Remove(DigitalCD.pCollectivesCol, info->Collective);
}

static const char* NodeBox_String(const OTreeNode* pNode)
{
	const BoxInfo* info = OTreeNode_GetData(pNode);

	if (info->Artist && *info->Artist)
		return SPrintf("%s (%s)", info->Box, info->Artist);

	return info->Box;
}

static void NodeBox_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	const OTreeNode* pNode = pObject;
	const char* pString = NodeBox_String(pNode);

	IGNORE(pOwner);

	psizes[0] = GetWidth_NodeSpriteString(pNode, pString);
}

static void NodeBox_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeNode* pNode = pObject;
	const BoxInfo* info = OTreeNode_GetData(pNode);
	const char* pString = NodeBox_String(pNode);

	IGNORE(pOwner);

	if (info->Collective == NULL)
		Plot_SpriteString(pNode, pItem, "n_compset", pString);
	else
		Plot_SpriteString(pNode, pItem, "n_set", pString);
}

static void NodeBox_GetArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	Node_GetSpriteStringArea(pObject, pOwner, pItem, pMouse, pArea);

	if (pMouse == NULL)
		return;

	if ((pArea->id == EWItemArea_Select) && Keyboard_PollAlt())
	{
		pArea->id = CDItemAction_EditBox;
		pArea->rect.x1 = pItem->m_box.x1;
	}
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeBox_VPtr =
{ { NodeBox_Plot
  , NodeBox_GetSize
  , Node_GetBox
  , NodeBox_GetArea
  }
, throw_NodeBox_AllocData
, NodeBox_ClearData
, NULL
, Compare_Childs
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static void throw_NodeArtist_AllocData(OTreeNode* This, void* pData, const void* pRefData)
{
	ArtistInfo* info = pData;
	const ArtistInfo* refinfo = pRefData;

	IGNORE(This);

	info->Key = refinfo->Key;
	info->Artist = throw_StrCol_Add(DigitalCD.pArtistsCol, refinfo->Artist);
	if (refinfo->Collective) info->Collective = throw_StrCol_Add(DigitalCD.pCollectivesCol, refinfo->Collective);
}

static void NodeArtist_ClearData(OTreeNode* This, void* pData)
{
	ArtistInfo* info = pData;

	IGNORE(This);

	StrCol_Remove(DigitalCD.pArtistsCol, info->Artist);
	StrCol_Remove(DigitalCD.pCollectivesCol, info->Collective);
}

static const char* NodeArtist_String(const OTreeNode* pNode)
{
	const ArtistInfo* info = OTreeNode_GetData(pNode);

	if (info->Collective && *info->Collective)
		return SPrintf("%s (%s)", info->Artist, info->Collective);

	return info->Artist;
}

static void NodeArtist_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	const OTreeNode* pNode = pObject;
	const char* pString = NodeArtist_String(pNode);

	IGNORE(pOwner);

	psizes[0] = GetWidth_NodeSpriteString(pNode, pString);
}

static void NodeArtist_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeNode* pNode = pObject;
	const char* pString = NodeArtist_String(pNode);

	IGNORE(pOwner);

	Plot_SpriteString(pNode, pItem, "n_artist", pString);
}

static void NodeArtist_GetArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	Node_GetSpriteStringArea(pObject, pOwner, pItem, pMouse, pArea);

	if (pMouse == NULL)
		return;

	if ((pArea->id == EWItemArea_Select) && Keyboard_PollAlt())
	{
		pArea->id = CDItemAction_EditAlbumAuthor;
		pArea->rect.x1 = pItem->m_box.x1;
	}
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeArtist_VPtr =
{ { NodeArtist_Plot
  , NodeArtist_GetSize
  , Node_GetBox
  , NodeArtist_GetArea
  }
, throw_NodeArtist_AllocData
, NodeArtist_ClearData
, NULL
, Compare_Childs
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static void throw_NodeCollective_AllocData(OTreeNode* This, void* pData, const void* pRefData)
{
	CollectiveInfo* info = pData;
	const CollectiveInfo* refinfo = pRefData;

	IGNORE(This);

	info->Key = refinfo->Key;
	info->Collective = throw_StrCol_Add(DigitalCD.pCollectivesCol, refinfo->Collective);
}

static void NodeCollective_ClearData(OTreeNode* This, void* pData)
{
	CollectiveInfo* info = pData;

	IGNORE(This);

	StrCol_Remove(DigitalCD.pCollectivesCol, info->Collective);
}

static void NodeCollective_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	const OTreeNode* pNode = pObject;
	const char* pString = ((const CollectiveInfo*) OTreeNode_GetData(pNode))->Collective;

	IGNORE(pOwner);

	psizes[0] = GetWidth_NodeSpriteString(pNode, pString);
}

static void NodeCollective_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeNode* pNode = pObject;
	const char* pString = ((const CollectiveInfo*) OTreeNode_GetData(pNode))->Collective;

	IGNORE(pOwner);

	Plot_SpriteString(pNode, pItem, "n_collective", pString);
}

static void NodeCollective_GetArea(const void* pObject, const void* pOwner, const WPlotItem* pItem, const Mouse* pMouse, WItemArea* pArea)
{
	Node_GetSpriteStringArea(pObject, pOwner, pItem, pMouse, pArea);

	if (pMouse == NULL)
		return;

	if ((pArea->id == EWItemArea_Select) && Keyboard_PollAlt())
	{
		pArea->id = CDItemAction_EditCollective;
		pArea->rect.x1 = pItem->m_box.x1;
	}
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeCollective_VPtr =
{ { NodeCollective_Plot
  , NodeCollective_GetSize
  , Node_GetBox
  , NodeCollective_GetArea
  }
, throw_NodeCollective_AllocData
, NodeCollective_ClearData
, NULL
, Compare_Childs
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static const char* NodeCDList_GetString(void)
{
	return "CDs";
}

static void NodeCDList_GetSize(const void* pObject, const void* pOwner, int* psizes)
{
	const OTreeNode* pNode = pObject;

	IGNORE(pOwner);

	psizes[0] = GetWidth_NodeSpriteString(pNode, NodeCDList_GetString());
}

static void NodeCDList_Plot(const void* pObject, const void* pOwner, const WPlotItem* pItem)
{
	const OTreeNode* pNode = pObject;
	const char* pString = NodeCDList_GetString();
	int bcol = 15;
	int fcol = 0;
	CRect box;
	CPoint pt;

	IGNORE(pOwner);

	Plot_Base(pNode, pItem, &box, NULL);

	Desktop_SetStdColour(bcol);
	// Plot filled rectangle
	Display_Plot(4, box.x0, box.y0);
	Display_Plot(101, box.x1, box.y1);

	// Plot Sprite
	pt.x = box.x0 + Sizes.Group.o.x0;
	pt.y = box.y0 + Sizes.Group.o.y0;
	Desktop_PlotNamedSprite("n_lists", pt, NULL);
	box.x0 += Sizes.Group.o.x1;

	// Plot text
	Desktop_SetStdColour(fcol);
	Desktop_SetStdColour(0x80 + bcol);
	box.x0 += Sizes.Text.x0;
	box.x1 = box.x0 + Desktop_GetTextWidth(pString);
	Desktop_PlotText(pString, &box);

	if (pItem->m_flags & EWItem_HasFocus)
	{
		Desktop_SetStdColour(fcol);
		// Plot filled rectangle
		Display_Plot(4, box.x0, box.y0 + 4);
		Display_Plot(101, box.x1, box.y0 + 7);
	}
}

static CSize NodeCDList_GetBox(const void* pObject, const void* pOwner, const int* pSizes, const int* pMaxSizes)
{
	const OTreeNode* pNode = pObject;
	const KeyInfo* pInfo = OTreeNode_GetData(pNode);

	if (pInfo->Key & CDTreeMode_Flat)
		return NodeFlatTrack_GetBox(pObject, pOwner, pSizes, pMaxSizes);

	return Node_GetBox(pObject, pOwner, pSizes, pMaxSizes);
}

//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeCDList_VPtr =
{ { NodeCDList_Plot
  , NodeCDList_GetSize
  , NodeCDList_GetBox
  , Node_GetArea
  }
, NULL
, NULL
, NULL
, Compare_Childs
};


//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

static const OTreeNodeVPtr NodeSearch_VPtr =
{ { NULL
  , NULL
  , NodeCDList_GetBox
  , NULL
  }
, NULL
, NULL
, NULL
, Compare_Childs
};

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

/**
 * This function is called in a loop to get the next child/grouping level
 * using the same ChildInfo block. This means that if they use the same layout,
 * attributes set at a higher level can be reused in the next levels.
 *
 * On a same level objects may use several attributes for display
 * and to differenciate/sort them. Let's take the album level: if two tracks
 * use the same album name, but have a different box, artist or collective,
 * then they belong to a different album.
 *
 * We combine all or part of the attributes to show distinct visuals
 * but if I group at several levels, info from the upper level becomes
 * redundant for the levels below and must be emptied in the ChildInfo block.
 * At display level I pay attention to check both that the attribute
 * is not NULL and does not point to an empty string before combining
 * it with other attributes but in fact querying attributes from the track
 * always replaces NULL by an empty string (&nil).
 *
 * Thus, I decided to mark compilation with a NULL in the "collective"
 * attribute. This means that when I empty an attribute used in an upper level
 * I use NULL in the "compilation" leveling path and a &nil in the other paths.
 * Visually it make no difference at text level, but still differentiate
 * with a different sprite and more importantly I can differentiate
 * between NULL and an empty string when sorting and so make a distinction
 * between comp/non-comp objects.
 *
 * Note:
 * We won't make a distinction between comp/non-comp collectives and artists.
 * This is implicit at collective level (since I cannot empty the text)
 * but at artist level we must be sure to use &nil instead of NULL.
 */
static void Track_GetSubEntity(const CDTrack* pTrack, ChildInfo* pInfo)
{
	const CDDesc* pDesc = CDTrack_GetCDDesc(pTrack);
	uint32_t key = pInfo->i.album.Key & CDTreeMode_Mask_Keys;
	uint32_t mode = pInfo->i.album.Key & ~CDTreeMode_Mask_Keys;

	switch (key)
	{
		case Key_Unknown:
		{
			// Prefill with all useful info
			pInfo->i.album.pDesc = pDesc;
			pInfo->i.album.AlbumId = CDDesc_GetId(pDesc);
			pInfo->i.album.Album = CDDesc_GetLabel(pDesc);
			pInfo->i.album.Date = CDDesc_GetMetaString(pDesc, EMetaId_StreamDate);
			pInfo->i.album.Box = CDDesc_GetMetaString(pDesc, EMetaId_StreamBox);
			pInfo->i.album.Artist = CDDesc_GetMetaString(pDesc, EMetaId_StreamArtist);
			pInfo->i.album.Collective = CDDesc_GetMetaString(pDesc, EMetaId_StreamCollective);

			// If artist at CD level, not a compilation
			if (*pInfo->i.album.Collective || *pInfo->i.album.Artist)
			{
				pInfo->i.album.Key = Key_NC | mode;
				Track_GetSubEntity(pTrack, pInfo);
				return;
			}

	// Entities belonging to a groupment by compilation
			// Discard CD artist & collective info
			pInfo->i.album.Artist = NULL;
			pInfo->i.album.Collective = NULL;

			if ((mode & CDTreeMode_GroupBy_Box)
			&&  *pInfo->i.album.Box)
			{
				pInfo->pVPtr = &NodeBox_VPtr;
				pInfo->size = sizeof(pInfo->i.box);
				pInfo->i.box.Key = Key_Comp_Box | mode;
				return;
			}
		}
		// no break;
		case Key_Comp_Box:
		{
			pInfo->pVPtr = &NodeAlbum_VPtr;
			pInfo->size = sizeof(pInfo->i.album);
			pInfo->i.album.Key = Key_Comp_Album | mode;
			// Discard box info
			if (mode & CDTreeMode_GroupBy_Box)
				pInfo->i.album.Box = NULL;
			return;
		}
		break;
		case Key_Comp_Album:
		{
			// put back artist & collective info, but from track instead of CD
			pInfo->i.album.Artist = CDTrack_GetMetaString(pTrack, EMetaId_StreamArtist);
			pInfo->i.album.Collective = CDTrack_GetMetaString(pTrack, EMetaId_StreamCollective);

			if ((mode & CDTreeMode_GroupBy_CompByArtist)
			&&  (mode & CDTreeMode_GroupBy_Collective)
			&&  *pInfo->i.album.Collective)
			{
				pInfo->pVPtr = &NodeCollective_VPtr;
				pInfo->size = sizeof(pInfo->i.collective);
				pInfo->i.collective.Key = Key_HComp_Collective | mode;
				return;
			}
		}
		// no break;
		case Key_HComp_Collective:
		{
			if ((mode & CDTreeMode_GroupBy_CompByArtist)
			&&  *pInfo->i.album.Artist)
			{
				pInfo->pVPtr = &NodeArtist_VPtr;
				pInfo->size = sizeof(pInfo->i.artist);
				pInfo->i.artist.Key = Key_HComp_Artist | mode;
				if ((mode & CDTreeMode_GroupBy_CompByArtist)
				&&  (mode & CDTreeMode_GroupBy_Collective))
					pInfo->i.artist.Collective = &nil; // Special case, not NULL
				return;
			}
		}
		// no break;
		case Key_HComp_Artist:
		{
			pInfo->pVPtr = &NodeTrack_VPtr;
			pInfo->size = sizeof(pInfo->i.track);
			pInfo->i.track.Key = Key_Track | mode | pInfo->flags;
			pInfo->i.track.pTrack = pTrack;
			return;
		}
		break;
	// Entities not part of a groupment by compilation
		case Key_NC:
		{
			if ((mode & CDTreeMode_GroupBy_Collective)
			&&  *pInfo->i.album.Collective)
			{
				pInfo->pVPtr = &NodeCollective_VPtr;
				pInfo->size = sizeof(pInfo->i.collective);
				pInfo->i.collective.Key = Key_Collective | mode;
				return;
			}
		}
        // no break;
        case Key_Collective:
		{
			if ((mode & CDTreeMode_GroupBy_Artist)
			&&  *pInfo->i.album.Artist)
			{
				pInfo->pVPtr = &NodeArtist_VPtr;
				pInfo->size = sizeof(pInfo->i.artist);
				pInfo->i.artist.Key = Key_Artist | mode;
				if (mode & CDTreeMode_GroupBy_Collective)
					pInfo->i.artist.Collective = &nil;
				return;
			}
		}
		// no break;
        case Key_Artist:
		{
			if ((mode & CDTreeMode_GroupBy_Box)
			&&  *pInfo->i.album.Box)
			{
				pInfo->pVPtr = &NodeBox_VPtr;
				pInfo->size = sizeof(pInfo->i.box);
				pInfo->i.box.Key = Key_Box | mode;
				if (mode & CDTreeMode_GroupBy_Artist)
					pInfo->i.box.Artist = &nil;
				if (mode & CDTreeMode_GroupBy_Collective)
					pInfo->i.artist.Collective = &nil;
				return;
			}
		}
		// no break;
        case Key_Box:
		{
			pInfo->pVPtr = &NodeAlbum_VPtr;
			pInfo->size = sizeof(pInfo->i.album);
			pInfo->i.album.Key = Key_Album | mode;
			if (mode & CDTreeMode_GroupBy_Box)
				pInfo->i.album.Box = &nil;
			if (mode & CDTreeMode_GroupBy_Artist)
				pInfo->i.box.Artist = &nil;
			if (mode & CDTreeMode_GroupBy_Collective)
				pInfo->i.artist.Collective = &nil;
			return;
		}
		break;
        case Key_Album:
		{
			pInfo->pVPtr = &NodeTrack_VPtr;
			pInfo->size = sizeof(pInfo->i.track);
			pInfo->i.track.Key = Key_Track | mode | pInfo->flags;
			pInfo->i.track.pTrack = pTrack;
			return;
		}
		break;
	}

	// Should not happen, but just in case
	pInfo->pVPtr = &NodeTrack_VPtr;
	pInfo->size = sizeof(pInfo->i.track);
	pInfo->i.track.Key = Key_Track | mode | pInfo->flags;
	pInfo->i.track.pTrack = pTrack;
}

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

OTreeNode* throw_CDTreeNodes_InsertTrack(OTreeList* pOList, const CDTrack* pTrack)
{
	OTreeNode* volatile pNode = OTreeList_GetRootNode(pOList);
	ChildInfo Info;
	uint32_t mode = ((const KeyInfo*) OTreeNode_GetData(pNode))->Key & ~CDTreeMode_Mask_Keys;

	Info.flags = 0;

	try
	{
		if (mode & CDTreeMode_Flat)
		{
			Info.size = sizeof(Info.i.track);
			Info.i.track.Key = Key_FlatTrack | mode | Info.flags;
			Info.i.track.pTrack = pTrack;
			Info.pVPtr = &NodeFlatTrack_VPtr;
			pNode = throw_OTreeNode_InsertChild(pNode, Info.pVPtr, Info.size, &Info.i);
		}
		else
		{
			Info.i.key.Key = Key_Unknown | mode;

			while ((Info.i.key.Key & CDTreeMode_Mask_Keys) < Key_Track)
			{
				Track_GetSubEntity(pTrack, &Info);

				pNode = throw_OTreeNode_InsertChild(pNode, Info.pVPtr, Info.size, &Info.i);
			}
		}
	}
	catch
	{
		CDTreeNodes_Remove(pOList, pNode);
		throw_current();
	}
	catch_end

	return pNode;
}

void CDTreeNodes_Remove(OTreeList* pOList, OTreeNode* pRefNode)
{
	const OTreeNode* pRootNode = OTreeList_GetRootNode(pOList);

	while (pRefNode != pRootNode)
	{
		OTreeNode* pNode = OTreeNode_GetParent(pRefNode);
		OTreeList_Remove(pOList, pRefNode);

		if (OTreeNode_CountChilds(pNode) != 0)
			break;

		pRefNode = pNode;
	}
}

OTreeNode* throw_CDTreeNodes_NewCDListNode(uint32_t proposedtype)
{
	uint32_t treetype = proposedtype;
	OTreeNode* pRootNode;

	pRootNode = throw_New_OTreeNode(&NodeCDList_VPtr, sizeof(treetype), (void*) &treetype);

	return pRootNode;
}

OTreeNode* throw_CDTreeNodes_NewSearchNode(uint32_t proposedtype)
{
	uint32_t treetype = proposedtype;

	return throw_New_OTreeNode(&NodeSearch_VPtr, sizeof(treetype), (void*) &treetype);
}

bool CDTreeNodes_IsCDDesc(const OTreeNode* pNode)
{
	const KeyInfo* info = OTreeNode_GetData(pNode);

	if ((info->Key & Key_Mask_Entity) != Entity_Album)
		return false;

	return true;
}

CDDesc* throw_CDTreeNodes_GetCDDesc(const OTreeNode* pNode)
{
	const AlbumInfo* info = OTreeNode_GetData(pNode);

	if ((info->Key & Key_Mask_Entity) != Entity_Album)
		throw_string("Tree Node is not a CD");

	// Read childs to get track info
	return (CDDesc*) info->pDesc;
}

bool CDTreeNodes_IsTrack(const OTreeNode* pNode)
{
	const KeyInfo* info = OTreeNode_GetData(pNode);

	if ((info->Key & Key_Mask_Entity) != Entity_Track)
		return false;

	return true;
}

CDTrack* throw_CDTreeNodes_GetTrack(const OTreeNode* pNode)
{
	const TrackInfo* info = OTreeNode_GetData(pNode);

	if ((info->Key & Key_Mask_Entity) != Entity_Track)
		throw_string("Tree Node is not a track");

	return (CDTrack*) info->pTrack;
}

const char* CDTreeNodes_GetField(const OTreeNode* pNode, EMetaId id, bool cd)
{
	const KeyInfo* pa = OTreeNode_GetData(pNode);

	switch(pa->Key & Key_Mask_Entity)
	{
		case Entity_Collective:
		{
			const CollectiveInfo* sa = (void*) pa;

			return sa->Collective;
		}
		break;
		case Entity_Artist:
		{
			const ArtistInfo* sa = (void*) pa;

			return sa->Artist;
		}
		break;
		case Entity_Box:
		{
			const BoxInfo* sa = (void*) pa;

			return sa->Box;
		}
		break;
		case Entity_Album:
		{
			const AlbumInfo* sa = (void*) pa;

			return CDDesc_GetMetaString(sa->pDesc, EMetaId_StreamAlbum);
		}
		break;
		case Entity_Track:
		{
			const TrackInfo* sa = (void*) pa;

			if (cd)
			{
				const CDDesc* pDesc = CDTrack_GetCDDesc(sa->pTrack);
				return CDDesc_GetMetaString(pDesc, id);
			}

			return CDTrack_GetMetaString(sa->pTrack, id);
		}
		break;
	}

	return &nil;
}

int CDTreeNodes_ShowNode(const OTreeNode* pNode, bool bForce)
{
	int Index = -1;

	if (pNode)
	{
		OTreeList* pOList = OTreeNode_GetTreeList(pNode);
		WListCore* pWList = OTreeList_GetWListCore(pOList);

		Index = OTreeList_ExpandParents(pOList, pNode);

		WListCore_Refresh(pWList, Index, Index);

		if (bForce)
			WListCore_ShowItem(pWList, Index);
	}

	return Index;
}

void CDTreeNodes_GetChildTracks(const OTreeNode* pNode, List* pList)
{
	if (!pNode) return;

	const TrackInfo* info = OTreeNode_GetData(pNode);

	if ((info->Key & Key_Mask_Entity) == Entity_Track)
		List_InsertBefore(pList, NULL, info->pTrack);
	else
	{
		for ( pNode = OTreeNode_GetFirstChild(pNode)
		    ; pNode != NULL
		    ; pNode = OTreeNode_GetSuccessor(pNode)
		    )
		{
			CDTreeNodes_GetChildTracks(pNode, pList);
		}
	}
}

void CDTreeNodes_GetChildDescs(const OTreeNode* pNode, List* pList)
{
	if (!pNode) return;

	const AlbumInfo* info = OTreeNode_GetData(pNode);
	uint32_t entity = info->Key & Key_Mask_Entity;

	if (entity > Entity_Album) return;
	if (entity == Entity_Album)
		List_InsertBefore(pList, NULL, info->pDesc);
	else
	{
		for ( pNode = OTreeNode_GetFirstChild(pNode)
		    ; pNode != NULL
		    ; pNode = OTreeNode_GetSuccessor(pNode)
		    )
		{
			CDTreeNodes_GetChildDescs(pNode, pList);
		}
	}
}
