#include "FLTrack.h"

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "swis.h"

#include "WimpLib:Exception.h"
#include "WimpLib:File.h"
#include "WimpLib:mem.h"
#include "WimpLib:Task.h"
#include "WimpLib:Utils.h"

#include "DigitalCD.h"
#include "DocEvents.h"
#include "FileList.h"
#include "MetaData.h"
#include "MetaScan.h"
#include "Options.h"

struct FLTrack
{
	FLTrack*          plink;
	FileList*         powner;
	FileList*         psublist;
	MetaList*         pmetas;
	char*             info;
	const char*       pathname;
	char*             leafname;
	char*             leafwithoutext;
	unsigned int      volume;
	int               points;
	unsigned int      repeats;
	unsigned int      flags;
	unsigned int      timflags;
	unsigned int      section;
	unsigned int      rate;
	const CSpriteHdr* pclue;
	char*             scannername;
};

FLTrack* throw_New_FLTrack(FileList* powner, unsigned int flags, const char* filename)
{
	FLTrack* This = throw_mem_calloc(sizeof(*This), 1);

	try
	{
		This->powner = powner;
		This->pmetas = throw_New_MetaList();

		This->info = throw_mem_allocstring(NULL);
		This->volume = 100;
		This->points = -1;
		This->repeats = 1;
		This->flags = flags;

		This->leafname = throw_mem_allocstring(NULL);
		if (!FLTrack_SetFilename(This, filename, true))
			throw_mem_nomem();
	}
	catch
	{
		This->powner = NULL;
		Delete_FLTrack(This);
		throw_current();
	}
	catch_end

	return This;
}

// Listen to the original track events so that we can reflect changes to it
static EListenerAction FLTrack_OnDocEvent(void* pListener, const Event* pEvent)
{
	FLTrack* This = pListener;
	const DocEvent* pe = pEvent->pData;

	switch(pe->event)
	{
		case EDocEvent_UpdateElement:
		{
			// The original changed, we must update our views
			try
			{
				DocEvent e = {EDocEvent_UpdateElement, This, NULL};
				DocEvents_NotifyListeners(pEvent->pSender, &e);
			}
			catch
			{
				App_ReportException();
			}
			catch_end
		}
		break;
		case EDocEvent_RemoveElement:
		{
			// As a link of that track, we must remove ourselves
			if (This->plink == pe->pElement)
			{
				if (This->powner)
					FileList_DelTrack(This->powner, This);
				else
					Delete_FLTrack(This);
			}
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

FLTrack* throw_New_FLTrack_Link(FileList* powner, const FLTrack* pOldTrack)
{
	FLTrack* This = throw_mem_calloc(sizeof(*This), 1);
	This->plink = (FLTrack*) FLTrack_ResolveLink(pOldTrack);
	This->powner = powner;

	throw_DocEvents_AddListener(This->plink, This, FLTrack_OnDocEvent);

	return This;
}

FLTrack* throw_New_FLTrack_Copy(FileList* powner, const FLTrack* pOldTrack)
{
	FLTrack* This = throw_mem_calloc(sizeof(*This), 1);

	pOldTrack = FLTrack_ResolveLink(pOldTrack);

	try
	{
		This->pmetas   = throw_New_MetaList_Copy(pOldTrack->pmetas);
		This->info     = throw_mem_allocstring(pOldTrack->info);
		This->volume   = pOldTrack->volume;
		This->points   = pOldTrack->points;
		This->repeats  = pOldTrack->repeats;
		This->flags    = pOldTrack->flags;
		This->timflags = pOldTrack->timflags;
		This->section  = pOldTrack->section;
		This->rate     = pOldTrack->rate;
		This->pclue    = pOldTrack->pclue;

		This->leafname = throw_mem_allocstring(NULL);
		if (!FLTrack_SetFilename(This, FLTrack_GetFilename(pOldTrack), true))
			throw_mem_nomem();

		This->powner = powner;
	}
	catch
	{
		This->powner = NULL;
		Delete_FLTrack(This);
		throw_current();
	}
	catch_end

	return This;
}

void Delete_FLTrack(FLTrack* This)
{
	if (This)
	{
		// Ensure no refresh occurs
		This->powner = NULL;

		// This line must always come first
		Delete_FileList(This->psublist);

		if (This->plink) DocEvents_RemoveListener(This->plink, This, FLTrack_OnDocEvent);

		mem_free(This->info);
		StrCol_Remove(DigitalCD.pPathsCol, This->pathname);
		mem_free(This->leafname);
		mem_free(This->leafwithoutext);
		mem_free(This->scannername);
		Delete_MetaList(This->pmetas);

		mem_free(This);
	}
}

FileList* FLTrack_GetOwner(const FLTrack* This)
{
	return This->powner;
}

const FLTrack* FLTrack_ResolveLink(const FLTrack* This)
{
	if (This->plink)
		return This->plink;

	return This;
}

FileList* FLTrack_GetSubList(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	return This->psublist;
}

const MetaList* FLTrack_GetMeta(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	return This->pmetas;
}

const char* FLTrack_GetMetaString(const FLTrack* This, EMetaId id)
{
	if (This->plink) This = This->plink;

	const MetaData* meta = MetaList_Find(This->pmetas, id);

	if (!meta || !meta->data)
		return &nil;

	return meta->data;
}

const char* FLTrack_GetInfo(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	if (!This->info)
		return &nil;

	return This->info;
}

unsigned int FLTrack_GetSection(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	return This->section;
}

unsigned int FLTrack_GetOrder(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	const MetaData* meta = MetaList_Find(This->pmetas, EMetaId_StreamTrackNumber);

	if (!meta)
		return 0;

	return (unsigned int) meta->data;
}

unsigned int FLTrack_GetRate(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	return This->rate;
}

unsigned int FLTrack_GetVolume(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	return This->volume;
}

int FLTrack_GetPoints(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	return This->points;
}

unsigned int FLTrack_GetRepeats(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	return This->repeats;
}

unsigned int FLTrack_GetFlags(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	return This->flags;
}

unsigned int FLTrack_GetTimFlags(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	return This->timflags;
}

const CSpriteHdr* FLTrack_GetClue(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	return This->pclue;
}

const char* FLTrack_GetFilename(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	if (This->powner
	&&  (This->pathname[0] == FILE_FOLDER_CHAR))
		return SPrintf("%s%s%s", FileList_GetBaseDir(This->powner), This->pathname, This->leafname);

	return SPrintf("%s%s", This->pathname, This->leafname);
}

const char* FLTrack_GetFullPathName(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	if (This->powner
	&&  (This->pathname[0] == FILE_FOLDER_CHAR))
		return SPrintf("%s%s", FileList_GetBaseDir(This->powner), This->pathname);

	return This->pathname;
}

const char* FLTrack_GetPathName(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	return This->pathname;
}

const char* FLTrack_GetLeafName(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	return This->leafname;
}

const char* FLTrack_GetTrackName(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	const char* ptitle = &nil;
	const MetaData* pmeta;

	if (This->flags & FLTrack_IsUrl)
	{
		if (This->flags & FLTrack_IsPlaylist)
		{
			pmeta = MetaList_Find(This->pmetas, EMetaId_StreamTitle);
			if (pmeta && pmeta->data) ptitle = pmeta->data;
		}
		else
		{
			pmeta = MetaList_Find(This->pmetas, EMetaId_StreamStation);
			if (pmeta && pmeta->data) ptitle = pmeta->data;
		}

		if (Options()->Music_Files.bShowFileAsTitle
		||  !*ptitle)
			return FLTrack_GetFilename(This);
	}
	else
	{
		pmeta = MetaList_Find(This->pmetas, EMetaId_StreamTitle);
		if (pmeta && pmeta->data) ptitle = pmeta->data;

		if (Options()->Music_Files.bShowFileAsTitle
		||  !*ptitle)
		{
			if (This->leafwithoutext && *This->leafwithoutext)
				return This->leafwithoutext;
			return This->leafname;
		}
	}

	return ptitle;
}

FLTrack_ObjectType FLTrack_GetObjectType(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	if (This->flags & FLTrack_IsUrl)
	{
		if (This->flags & FLTrack_IsPlaylist)
			return FLTrack_Type_YellowPage;
		else
			return FLTrack_Type_Url;
	}
	else if (This->flags & FLTrack_IsDir)
		return FLTrack_Type_Directory;
	else if (This->flags & FLTrack_IsPlaylist)
		return FLTrack_Type_FileList;

	return FLTrack_Type_File;
}

bool FLTrack_IsContainer(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	if ((This->flags & FLTrack_IsDir)
	||  (This->flags & FLTrack_IsPlaylist))
		return true;

	return false;
}

bool FLTrack_IsPlayable(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	// Track itself must be playable
	if (This->flags & (FLTrack_MustNotPlay | FLTrack_NeverPlay))
		return false;

	// If contained in a list, the list itself must be playable
	if (This->powner) return FileList_IsPlayable(This->powner);

	return true;
}

void FLTrack_OpenFileDir(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	char* pch;
	char* pc;

	switch(FLTrack_GetObjectType(This))
	{
		case FLTrack_Type_File:
		case FLTrack_Type_FileList:
		{
			pch = SPrintf("Filer_OpenDir %s", FLTrack_GetFilename(This));
			pc = strrchr(pch, FILE_FOLDER_CHAR);
			*pc = 0;

			_swix(OS_CLI, _IN(0), pch);
		}
		break;
		case FLTrack_Type_Directory:
		{
			pch = SPrintf("Filer_OpenDir %s", FLTrack_GetFilename(This));

			_swix(OS_CLI, _IN(0), pch);
		}
		break;
	}
}

void throw_FLTrack_DeleteFile(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	if (FLTrack_GetObjectType(This) == FLTrack_Type_File)
		throw_os(File_Delete(FLTrack_GetFilename(This)));
}

void FLTrack_AttachSubList(FLTrack* This, FileList* pSubList)
{
	if (This->plink) This = This->plink;

	This->psublist = pSubList;
}

bool FLTrack_SetMetaText(FLTrack* This, EMetaId id, EMetaOrigin o, const char* ps)
{
	if (This->plink) This = This->plink;

	bool b = false;
	try
	{
		b = throw_MetaList_SetText(This->pmetas, id, o, ps);
	}
	catch
	{
		b = false;
	}
	catch_end

	return b;
}

bool FLTrack_SetInfo(FLTrack* This, const char* info)
{
	if (This->plink) This = This->plink;

	return mem_setstring((const char**) &This->info, info);
}

bool FLTrack_SetSection(FLTrack* This, unsigned int section)
{
	if (This->plink) This = This->plink;

	unsigned int oldvalue = This->section;

	This->section = FitInRange(section, 0, 99);

	return (oldvalue != This->section);
}

bool FLTrack_SetOrder(FLTrack* This, EMetaOrigin o, unsigned int order)
{
	if (This->plink) This = This->plink;

	bool b = false;
	try
	{
		b = throw_MetaList_SetData
		        ( This->pmetas, EMetaId_StreamTrackNumber, o
		        , (void*) FitInRange(order, 0, 9999), 0
		        );
	}
	catch
	{
		b = false;
	}
	catch_end

	return b;
}

bool FLTrack_SetRate(FLTrack* This, unsigned int rate)
{
	if (This->plink) This = This->plink;

	unsigned int oldvalue = This->rate;

	This->rate = FitInRange(rate, 0, 999);

	return (oldvalue != This->rate);
}

bool FLTrack_SetVolume(FLTrack* This, unsigned int volume)
{
	if (This->plink) This = This->plink;

	unsigned int oldvalue = This->volume;

	This->volume = FitInRange(volume, 10, 500);

	return (oldvalue != This->volume);
}

bool FLTrack_SetPoints(FLTrack* This, int points)
{
	if (This->plink) This = This->plink;

	int oldvalue = This->points;

	This->points = FitInRange(points, -1, Options()->PlayLists.MaxPoints);

	return (oldvalue != This->points);
}

bool FLTrack_SetRepeats(FLTrack* This, unsigned int repeats)
{
	if (This->plink) This = This->plink;

	unsigned int oldvalue = This->repeats;

	This->repeats = FitInRange(repeats, 1, 255);

	return (oldvalue != This->repeats);
}

bool FLTrack_SetFlags(FLTrack* This, unsigned int value, unsigned int mask)
{
	if (This->plink) This = This->plink;

	unsigned int newVal;

	newVal = This->flags & ~mask;
	newVal |= value & mask;

	if (newVal == This->flags) return false;

	This->flags = newVal;

	return true;
}

bool FLTrack_SetTimFlags(FLTrack* This, unsigned int value, unsigned int mask)
{
	if (This->plink) This = This->plink;

	unsigned int newVal;

	newVal = This->timflags & ~mask;
	newVal |= value & mask;

	if (newVal == This->timflags) return false;

	This->timflags = newVal;

	return true;
}

bool FLTrack_SetClue(FLTrack* This, const CSpriteHdr* pclue)
{
	if (This->plink) This = This->plink;

	bool bret = (This->pclue != pclue);

	if (bret) This->pclue = pclue;

	return bret;
}

bool FLTrack_SetFilename(FLTrack* This, const char* filename, bool bForce)
{
	if (This->plink) This = This->plink;

	const char* base;
	const char* tmp;
	char* pch;
	char* leaf;
	bool bleaf = false;
	bool bUseBase;

	if (!bForce
	&&  !strcmp(nvl(filename), FLTrack_GetFilename(This)))
		return false;

	if (This->flags & FLTrack_IsUrl)
	{
		pch = SPrintf("%s", filename);
		leaf = strstr(pch, "://");
		if (leaf) leaf = strchr(leaf + 3, '/');
		if (leaf)
			leaf++;
		else
			leaf = pch;
	}
	else
	{
		if (This->powner)
		{
			tmp = filename;
			base = FileList_GetBaseDir(This->powner);
			bUseBase = (*base != 0);
			// Check if name starts with filelist's basedir
			while(*base)
			{
				if (*base++ != *tmp++)
				{
					bUseBase = false;
					break;
				}
			}
			if (bUseBase) filename = tmp;
		}
		pch = SPrintf("%s", filename);
		leaf = (char*) File_GetLeafName(pch);
	}

	tmp = mem_allocstring(leaf);
	if (!tmp) return false;
	if (leaf) *leaf = 0;

	try
	{
		throw_StrCol_Set(DigitalCD.pPathsCol, &This->pathname, pch);
		mem_free(This->leafname);
		This->leafname = (char*) tmp;
		mem_free(This->leafwithoutext);
		This->leafwithoutext = NULL;

		if (!(This->flags & FLTrack_IsUrl))
		{
			pch = strrchr(tmp, FILE_EXTENSION_CHAR);
			if (tmp < pch)
				bleaf = true;

			if (bleaf)
			{
				This->leafwithoutext = mem_allocstring(tmp);
				if (This->leafwithoutext && (tmp < pch))
				{
					This->leafwithoutext[pch - tmp] = 0;
				}
			}
		}
	}
	catch
	{
		App_ReportException();
		mem_free(tmp);
		return false;
	}
	catch_end

	return true;
}

MetaScan* FLTrack_GetScanner(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	return MetaScanList_Find(This->scannername);
}

const char* FLTrack_GetScannerName(const FLTrack* This)
{
	if (This->plink) This = This->plink;

	return This->scannername;
}

bool FLTrack_SetScannerName(FLTrack* This, const char* id)
{
	if (This->plink) This = This->plink;

	return mem_setstring((const char**) &This->scannername, id);
}

bool FLTrack_ScanFilename(FLTrack* This, MetaScan* pScanner)
{
	if (This->plink) This = This->plink;

	if ((FLTrack_GetObjectType(This) != FLTrack_Type_File)
	||  !This->powner
	||  (This->pathname[0] != FILE_FOLDER_CHAR)) // must use basedir
		return false;

	return MetaScan_Scan(pScanner, This, This->pathname, This->leafname);
}

unsigned int FLTrack_DecodeFlags(const char* params)
{
	const char* pnext;
	const char* p = params;
	unsigned int flags = 0;

	// First scan to locate flags
	while (p && *p)
	{
		// \ and , in parameters are prefixed by a \ character
		for(pnext = p; *pnext && (*pnext != ','); pnext++)
		{
			if (*pnext == '\\') pnext++;
		}

		if (*pnext) pnext++;

		switch(p[0])
		{
			case 'F':
			case 'f':
			{
				flags = atoi(p + 1) & FLTrack_LoadMask;
			}
			break;
		}
		p = pnext;
	}

	return flags;
}

void FLTrack_DecodeParameters(FLTrack* This, char* params)
{
	char* pnext;
	char* p = params;
	FLTrack_ObjectType type;

	if (This->plink) This = This->plink;

	type = FLTrack_GetObjectType(This);

	while (params && *params)
	{
		// \ and , in parameters are prefixed by a \ character
		for(pnext = p = params; *pnext && (*pnext != ',');)
		{
			if (*pnext == '\\') pnext++;
			*p++ = *pnext++;
		}

		if (*pnext) pnext++;
		*p = '\0';

		switch(params[0])
		{
			case 'A':
			case 'a':
			{
				if (type == FLTrack_Type_Url)
					FLTrack_SetMetaText(This, EMetaId_StreamBroadcaster, EMetaOrigin_User, params + 1);
				else if (type != FLTrack_Type_YellowPage)
					FLTrack_SetMetaText(This, EMetaId_StreamArtist, EMetaOrigin_User, params + 1);
			}
			break;
			case 'B':
			case 'b':
			{
				if (type == FLTrack_Type_File)
					FLTrack_SetMetaText(This, EMetaId_StreamAlbum, EMetaOrigin_User, params + 1);
			}
			break;
			case 'C':
			case 'c':
			{
				FLTrack_SetInfo(This, params + 1);
			}
			break;
			case 'D':
			case 'd':
			{
				if (type == FLTrack_Type_File)
					FLTrack_SetSection(This, atoi(params + 1));
			}
			break;
			case 'E':
			case 'e':
			{
				if (type == FLTrack_Type_File)
					FLTrack_SetMetaText(This, EMetaId_StreamCollective, EMetaOrigin_User, params + 1);
			}
			break;
			case 'G':
			case 'g':
			{
				if (type == FLTrack_Type_File)
					FLTrack_SetMetaText(This, EMetaId_StreamBox, EMetaOrigin_User, params + 1);
			}
			break;
			case 'H':
			case 'h':
			{
				if (type == FLTrack_Type_File)
					FLTrack_SetTimFlags(This, atoi(params + 1), 0xffffffff);
			}
			break;
			case 'M':
			case 'm':
			{
				if (type == FLTrack_Type_Url)
					FLTrack_SetMetaText(This, EMetaId_StreamMimeType, EMetaOrigin_User, params + 1);
			}
			break;
			case 'O':
			case 'o':
			{
				if (type == FLTrack_Type_File)
					FLTrack_SetOrder(This, EMetaOrigin_User, atoi(params + 1));
			}
			break;
			case 'P':
			case 'p':
			{
				if ((type == FLTrack_Type_File)
				||  (type == FLTrack_Type_Url))
					FLTrack_SetPoints(This, atoi(params + 1));
			}
			break;
			case 'R':
			case 'r':
			{
				if (type == FLTrack_Type_Url)
					FLTrack_SetRate(This, atoi(params + 1));
			}
			break;
			case 'S':
			case 's':
			{
				if ((type == FLTrack_Type_File)
				||  (type == FLTrack_Type_Url))
					This->pclue = Sprites_SelectSprite(DigitalCD.Clues, params + 1);
			}
			break;
			case 'T':
			case 't':
			{
				if (type == FLTrack_Type_Url)
					FLTrack_SetMetaText(This, EMetaId_StreamStation, EMetaOrigin_User, params + 1);
				else
					FLTrack_SetMetaText(This, EMetaId_StreamTitle, EMetaOrigin_User, params + 1);
			}
			break;
			case 'V':
			case 'v':
			{
				if ((type == FLTrack_Type_File)
				||  (type == FLTrack_Type_Url))
					FLTrack_SetVolume(This, atoi(params + 1));
			}
			break;
			case 'Y':
			case 'y':
			{
				// Valid date ? The check is performed in SetMetaText.
				if (type == FLTrack_Type_File)
					FLTrack_SetMetaText(This, EMetaId_StreamDate, EMetaOrigin_User, params + 1);
			}
			break;
			case 'Z':
			case 'z':
			{
				if ((type == FLTrack_Type_FileList)
				||  (type == FLTrack_Type_Directory))
					FLTrack_SetScannerName(This, params + 1);
			}
			break;
		}
		params = pnext;
	}
}

static bool FLTrack_MustSave(bool bAll, const MetaData* pmeta)
{
	if (pmeta)
	{
		if (pmeta->id == EMetaId_StreamTrackNumber)
		{
			if (pmeta->data)
			{
				if (bAll || (pmeta->origin == EMetaOrigin_User) || (pmeta->origin == EMetaOrigin_Stream))
					return true;
			}
		}
		else if (pmeta->data[0])
		{
			if (bAll || (pmeta->origin == EMetaOrigin_User) || (pmeta->origin == EMetaOrigin_Stream))
				return true;
		}
	}

	return false;
}

static char* write_string(char* pstring, char tag, const char* p)
{
	for (*pstring++ = tag; *p;)
	{
		if ((*p == ',')
		||  (*p == '\\'))
			*pstring++ = '\\';
		*pstring++ = *p++;
	}
	*pstring++ = ',';

	return pstring;
}

void FLTrack_SaveParameters(const FLTrack* This, char* pstring, bool bAll)
{
	if (This->plink) This = This->plink;

	const MetaData* pmeta;
	int count = 0;

	pmeta = MetaList_Find(This->pmetas, EMetaId_StreamArtist);
	if (!pmeta)	pmeta = MetaList_Find(This->pmetas, EMetaId_StreamBroadcaster);
	if (FLTrack_MustSave(bAll, pmeta))
	{
		pstring = write_string(pstring, 'a', pmeta->data);
		count++;
	}

	pmeta = MetaList_Find(This->pmetas, EMetaId_StreamAlbum);
	if (FLTrack_MustSave(bAll, pmeta))
	{
		pstring = write_string(pstring, 'b', pmeta->data);
		count++;
	}

	if (This->info && This->info[0])
	{
		pstring = write_string(pstring, 'c', This->info);
		count++;
	}

	if (This->section != 0)
	{
		sprintf(pstring, "d%d,", This->section);
		pstring += strlen(pstring);
		count++;
	}

	pmeta = MetaList_Find(This->pmetas, EMetaId_StreamCollective);
	if (FLTrack_MustSave(bAll, pmeta))
	{
		pstring = write_string(pstring, 'e', pmeta->data);
		count++;
	}

	if (This->flags & FLTrack_LoadMask)
	{
		sprintf(pstring, "f%d,", This->flags & FLTrack_LoadMask);
		pstring += strlen(pstring);
		count++;
	}

	pmeta = MetaList_Find(This->pmetas, EMetaId_StreamBox);
	if (FLTrack_MustSave(bAll, pmeta))
	{
		pstring = write_string(pstring, 'g', pmeta->data);
		count++;
	}

	if (This->timflags)
	{
		sprintf(pstring, "h%d,", This->timflags);
		pstring += strlen(pstring);
		count++;
	}

	pmeta = MetaList_Find(This->pmetas, EMetaId_StreamMimeType);
	if (FLTrack_MustSave(bAll, pmeta))
	{
		pstring = write_string(pstring, 'm', pmeta->data);
		count++;
	}

	if (This->points != -1)
	{
		sprintf(pstring, "p%d,", This->points);
		pstring += strlen(pstring);
		count++;
	}

	pmeta = MetaList_Find(This->pmetas, EMetaId_StreamTrackNumber);
	if (FLTrack_MustSave(bAll, pmeta))
	{
		sprintf(pstring, "o%d,", (unsigned int) pmeta->data);
		pstring += strlen(pstring);
		count++;
	}
	if (This->rate != 0)
	{
		sprintf(pstring, "r%d,", This->rate);
		pstring += strlen(pstring);
		count++;
	}
	if (This->pclue)
	{
		char text[13];
		int i;

		for (i = 0; (i < 12) && (This->pclue->name[i] >= 32); i++)
			text[i] = This->pclue->name[i];
		text[i] = 0;

		sprintf(pstring, "s%s,", text);
		pstring += strlen(pstring);
		count++;
	}

	pmeta = MetaList_Find(This->pmetas, EMetaId_StreamTitle);
	if (!pmeta)	pmeta = MetaList_Find(This->pmetas, EMetaId_StreamStation);
	if (FLTrack_MustSave(bAll, pmeta))
	{
		pstring = write_string(pstring, 't', pmeta->data);
		count++;
	}

	if (This->volume != 100)
	{
		sprintf(pstring, "v%d,", This->volume);
		pstring += strlen(pstring);
		count++;
	}

	pmeta = MetaList_Find(This->pmetas, EMetaId_StreamDate);
	if (FLTrack_MustSave(bAll, pmeta))
	{
		pstring = write_string(pstring, 'y', pmeta->data);
		count++;
	}

	if (This->scannername && This->scannername[0])
	{
		sprintf(pstring, "z%s,", This->scannername);
		pstring += strlen(pstring);
		count++;
	}

	if (count)
		pstring[-1] = 0;
	else
		pstring[0] = 0;
}

void FLTrack_RefreshViews(const FLTrack* This, void* pSender, bool bSetAutoModified, bool bSetModified)
{
	if (This->plink) This = This->plink;

	if (This->powner)
	{
		try
		{
			DocEvent e = {EDocEvent_UpdateElement, This, NULL};
			DocEvents_NotifyListeners(pSender, &e);
		}
		catch
		{
			App_ReportException();
		}
		catch_end

		if (bSetAutoModified) FileList_SetAutoModifiedFlag(This->powner);
		if (bSetModified) FileList_SetModifiedFlag(This->powner, true);
	}
}


// http://[user[:password]@]host[:port][/path]

int FLTrack_IsValidUrl(const char* url)
{
	const char* ptr;

	if (strncmp("http://", nvl(url), 7))
		return 0;

	url += 7;
	ptr = strchr(url, '/');
	if (!ptr) return 1;
	if (ptr[1]) return 3;

	return 2;
}
