#include "MetaScan.h"

#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

//#include "WimpLib:Log.h"
#include "WimpLib:std.h"
#include "WimpLib:exception.h"
#include "WimpLib:mem.h"
#include "WimpLib:File.h"
#include "WimpLib:Menu.h"
#include "WimpLib:Message.h"
#include "WimpLib:Task.h"
#include "WimpLib:Throwback.h"
#include "WimpLib:utils.h"

#include "FileTypes.h"

/*
 * A scanner is composed of parser nodes is the form:
 *
 *  root -> node -> ... -> end
 *   |
 *   v
 *  root -> node -> ... -> end
 *   |
 *   v
 *  root -> node -> ... -> end
 *   |
 *
 * If the node is an alternative it as the first alternative as child,
 * and each root child as the next alternative as child:
 *  node -> alt -> node
 *           |
 *           v
 *          root -> node -> ... -> end
 *           |
 *           v
 *          root -> node -> ... -> end
 *           |
 *
 * Note: the nodes in an alternative have the same node as parent.
 *
 * If the node is an option it as the option node as child:
 *
 *  node -> opt -> node
 *           |
 *           v
 *          root -> node -> ... -> end
 *
 * Note: the nodes in the option have the option node as parent.
 *
 */
typedef enum
{ EParserNode_Type_Root
, EParserNode_Type_End
, EParserNode_Type_None
// Containers
, EParserNode_Type_Optional
, EParserNode_Type_Alternative
// Others
, EParserNode_Type_Space
, EParserNode_Type_Comp
, EParserNode_Type_Constant
, EParserNode_Type_Variable
, EParserNode_Type_VarLeaf
, EParserNode_Type_VarInt
, EParserNode_Type_VarDate
, EParserNode_Type_VarExt
} EParserNode_Type;

static const char* ParserNode_TypeTxt[] =
{ "<ROOT"
, "END> "
, ""
, "OPT  "
, "ALT  "
, "SPACE"
, "COMP "
, "CONST"
, "VAR  "
, "INT  "
, "DATE "
, "EXT  "
};

typedef struct VarName
{
	const char*       pName;
	EParserNode_Type  type;
	EMetaId           id;
} VarName;

static const VarName VarList[] =
{ {"space"     , EParserNode_Type_Space   , EMetaId_None            }
, {"track"     , EParserNode_Type_VarInt  , EMetaId_StreamOrder     }
, {"ext"       , EParserNode_Type_VarExt  , EMetaId_None            }
, {"date"      , EParserNode_Type_VarDate , EMetaId_StreamDate      }
, {"title"     , EParserNode_Type_Variable, EMetaId_StreamTitle     }
, {"album"     , EParserNode_Type_Variable, EMetaId_StreamAlbum     }
, {"box"       , EParserNode_Type_Variable, EMetaId_StreamBox       }
, {"artist"    , EParserNode_Type_Variable, EMetaId_StreamArtist    }
, {"collective", EParserNode_Type_Variable, EMetaId_StreamCollective}
, {"any"       , EParserNode_Type_Variable, EMetaId_None            }
, {"comp"      , EParserNode_Type_Comp    , EMetaId_None            }
, {NULL        , EParserNode_Type_Space   , EMetaId_None            }
};

typedef enum
{ EParser_Eval_Stacked
, EParser_Eval_Ok
, EParser_Eval_Unstack
, EParser_Eval_Done
} EParser_Eval;

typedef struct ParserNode ParserNode;

struct ParserNode
{
	EParserNode_Type  type;
	ParserNode*       pParent;
	ParserNode*       pNext;
	ParserNode*       pChild;
	char*             pText;
	int               len;
};

typedef struct NodeStack
{
	const ParserNode* pNode;
	char*             pStart;
	char*             pEnd;
	bool              bDone;
	int               flags;
} NodeStack;

struct MetaScan
{
	char*       filename;
	char*       title;
	bool        bDayFirst;
	struct
	{
		struct
		{
			char* from;
			char* to;
		} pre;
		struct
		{
			char* from;
			char* to;
		} post;
		char    join;
		char    skip;
	} replace;
	ParserNode* Paths;
	ParserNode* Leafs;
	NodeStack   Stack[50];
	NodeStack*  pStackNode;
};

/*----------------------------
 * MetaScan file parsing
 */

static ParserNode* Parser_Ensure(ParserNode* pItem, EParserNode_Type type, char* pfirst)
{
	ParserNode* pNew = pItem;

	// Change of type? save texts
	if (pItem)
	{
		switch(pItem->type)
		{
			case EParserNode_Type_Constant:
			{
				// Must not be completed yet
				if(pItem->len == 0)
				{
					// Extend constant?
					if (type == pItem->type)
						return pItem;

					pItem->len = pItem->pText - pfirst;
					// Allocate text
					pItem->pText = throw_mem_allocprint
					                  ( "%.*s"
					                  , pItem->len
					                  , pfirst
					                  );
				}
			}
			break;
			case EParserNode_Type_Variable:
			case EParserNode_Type_VarLeaf:
			{
				// Must not be completed yet
				if(pItem->len == 0)
				{
					pItem->len = pItem->pText - pfirst;
					// Allocate text
					pItem->pText = throw_mem_allocprint
					                  ( "%.*s"
					                  , pItem->len
					                  , pfirst
					                  );

					// Convert type
					const VarName* pVar;

					for (pVar = &VarList[0]; pVar->pName; pVar++)
					{
						if (!strcmp(pVar->pName, pItem->pText))
						{
							// Stay o current type if new type is Variable (cf. VarLeaf)
							if (pVar->type != EParserNode_Type_Variable)
								pItem->type = pVar->type;
							break;
						}
					}
				}
			}
			break;
		}
	}

	if (type != EParserNode_Type_Root)
	{
		if (!pItem)
			throw_string("Internal: Trying to insert ParseNode %s without a predecessor", ParserNode_TypeTxt[type]);
		else
		{
			if ((type != EParserNode_Type_None)
			&&  (pItem->pNext != NULL))
				throw_string("Internal: ParseNode %s(%s) with already assigned follower", ParserNode_TypeTxt[pItem->type], pItem->pText);
		}
	}

	switch(type)
	{
		case EParserNode_Type_Root:
		{
			pNew = throw_mem_calloc(sizeof(*pNew), 1);
			pNew->type = EParserNode_Type_Root;
			pNew->pText = (char*) &nil;

			if (pItem)
			{
				if ((pItem->type != EParserNode_Type_Root)
				&&  (pItem->type != EParserNode_Type_Alternative)
			    &&  (pItem->type != EParserNode_Type_Optional))
		    		throw_string("Internal: trying to add a child to ParseNode %s(%s)", ParserNode_TypeTxt[pItem->type], pItem->pText);

				if (pItem->pChild != NULL)
		    		throw_string("Internal: ParseNode %s(%s) as alreay a child", ParserNode_TypeTxt[pItem->type], pItem->pText);

				pNew->pParent = pItem;
				pItem->pChild = pNew;
			}
		}
		break;
		case EParserNode_Type_End:
		{
			pNew = throw_mem_calloc(sizeof(*pNew), 1);
			pNew->type = EParserNode_Type_End;
			pNew->pParent = pItem->pParent;
			pNew->pText = (char*) &nil;

			pItem->pNext = pNew;
		}
		break;
		case EParserNode_Type_None:
		{
			// Keep current node
			return pItem;
		}
		break;
		case EParserNode_Type_Optional:
		case EParserNode_Type_Alternative:
		{
			// Add new item of given type to the end
			pNew = throw_mem_calloc(sizeof(*pNew), 1);
			pNew->type = type;
			pNew->pParent = pItem->pParent;
			pNew->pText = (char*) &nil;
			pItem->pNext = pNew;

			// If new item is container, allocate a root child
			pNew = Parser_Ensure(pNew, EParserNode_Type_Root, pfirst);
		}
		break;
		default:
		{
			if ((type < EParserNode_Type_Space)
			||  (type > EParserNode_Type_VarExt))
				throw_string("Internal: trying to add an unkown ParserNode type %d", type);

			// Add new item of given type to the end
			pNew = throw_mem_calloc(sizeof(*pNew), 1);
			pNew->type = type;
			pNew->pParent = pItem->pParent;
			pItem->pNext = pNew;

			pNew->pText = pfirst;
			pNew->len = 0;
		}
	}

	return pNew;
}

static ParserNode* New_ParserNode(ParserNode* pParent, int line, const char* buffer)
{
	ParserNode* pItem = Parser_Ensure(pParent, EParserNode_Type_Root, NULL);
	const char* pc = buffer;
	char word[256];

	// On next call Root node needs to be attached as child of this node
	pParent = pItem;

	while (*pc)
	{
		switch(*pc)
		{
			case '#':
			{
				// Append new leaf variable node
				pItem = Parser_Ensure(pItem, EParserNode_Type_VarLeaf, word);
			}
			break;
			case '%':
			{
				// Append new variable node
				pItem = Parser_Ensure(pItem, EParserNode_Type_Variable, word);
			}
			break;
			case '\\':
			{
				// Append new constant node or extend current constant node with '\'
				pItem = Parser_Ensure(pItem, EParserNode_Type_Constant, word);
				*pItem->pText++ = pc[1];
				pc += 1;
			}
			break;
			case '[':
			{
				// Insert an optional with a root node as child
				// and use child as current node
				pItem = Parser_Ensure(pItem, EParserNode_Type_Optional, word);
			}
			break;
			case ']':
			{
				// Parent should an the optional
				if (!pItem->pParent
				||  (pItem->pParent->type != EParserNode_Type_Optional))
				{
					Throwback_Error(line, pc - buffer + 1, "Misplaced ]");
					throw_string("%s", "parsing error");
				}

				// Optional should not be empty
				if (pItem->type == EParserNode_Type_Root)
				{
					Throwback_Error(line, pc - buffer + 1, "Empty [ ]");
					throw_string("%s", "parsing error");
				}

				// Eventually complete last variable/constant
				Parser_Ensure(pItem, EParserNode_Type_End, word);

				// Use parent as current node
				pItem = pItem->pParent;
			}
			break;
			case '(':
			{
				// Insert an alternative with a root node as child
				// and use child as current node
				pItem = Parser_Ensure(pItem, EParserNode_Type_Alternative, word);
			}
			break;
			case '|':
			{
				ParserNode* pnode = pItem->pParent;

				// Try to move up to alternative node
				while (pnode && (pnode->type == EParserNode_Type_Root))
					 pnode = pnode->pParent;

				if (!pnode
				||  (pnode->type != EParserNode_Type_Alternative))
				{
					Throwback_Error(line, pc - buffer + 1, "Misplaced |");
					throw_string("%s", "parsing error");
				}

				// Alternative should not be empty
				if (pItem->type == EParserNode_Type_Root)
				{
					Throwback_Error(line, pc - buffer + 1, "Empty alternative ( | )");
					throw_string("%s", "parsing error");
				}

				// Eventually complete last variable/constant
				Parser_Ensure(pItem, EParserNode_Type_End, word);

				// Move to last child of alternative node
				while (pnode->pChild)
					pnode = pnode->pChild;

				// Insert root node as new child and use it as current node
				pItem = Parser_Ensure(pnode, EParserNode_Type_Root, word);
			}
			break;
			case ')':
			{
				ParserNode* parent = pItem->pParent;

				// Try to move up to alternative
				while (parent && (parent->type == EParserNode_Type_Root))
					 parent = parent->pParent;

				if (!parent
				||  (parent->type != EParserNode_Type_Alternative))
				{
					Throwback_Error(line, pc - buffer + 1, "Misplaced )");
					throw_string("%s", "parsing error");
				}

				// Alternative should not be empty
				if (pItem->type == EParserNode_Type_Root)
				{
					Throwback_Error(line, pc - buffer + 1, "Empty alternative ( | )");
					throw_string("%s", "parsing error");
				}

				// Eventually complete last variable/constant
				Parser_Ensure(pItem, EParserNode_Type_End, word);

				// Use this node as current node
				pItem = parent;
			}
			break;
			case ' ':
			{
				// mark last variable/constant as done
				pItem = Parser_Ensure(pItem, EParserNode_Type_None, word);
			}
			break;
			default:
			{
				// Extend current variable or constant node with given char
				// or define new constant node and assign it given char
				if ((pItem->type >= EParserNode_Type_Variable)
				&&  (pItem->len == 0))
					*pItem->pText++ = *pc;
				else
				{
					pItem = Parser_Ensure(pItem, EParserNode_Type_Constant, word);
					*pItem->pText++ = *pc;
				}
			}
		}

		pc++;
	}

	// Eventually complete last variable/constant
	Parser_Ensure(pItem, EParserNode_Type_End, word);

	// Check that we did not forget some closing bracket
	while (pItem->pParent)
	{
		pItem = pItem->pParent;

		if (pItem->type == EParserNode_Type_Optional)
		{
			Throwback_Error(line, 0, "Missing ]");
			throw_string("%s", "parsing error");
		}
		else if (pItem->type == EParserNode_Type_Alternative)
		{
			Throwback_Error(line, 0, "Missing )");
			throw_string("%s", "parsing error");
		}
		else if (pItem->type != EParserNode_Type_Root)
			throw_string("Internal: undexpected ParseNode %s(%s) as parent", ParserNode_TypeTxt[pItem->type], pItem->pText);
	}

	return pParent;
}

static void Delete_ParserNodes(ParserNode* pItem)
{
	if (pItem)
	{
		if (pItem->pNext) Delete_ParserNodes(pItem->pNext);
		if (pItem->pChild) Delete_ParserNodes(pItem->pChild);

		mem_free(pItem->pText);
		mem_free(pItem);
	}
}

static void Delete_MetaScan(MetaScan* This)
{
	if (This)
	{
		mem_free(This->filename);
		mem_free(This->title);
		mem_free(This->replace.pre.from);
		mem_free(This->replace.pre.to);
		mem_free(This->replace.post.from);
		mem_free(This->replace.post.to);

		Delete_ParserNodes(This->Paths);
		Delete_ParserNodes(This->Leafs);

		mem_free(This);
	}
}

static MetaScan* New_MetaScan(const char* filename)
{
	MetaScan* This = throw_mem_calloc(sizeof(*This), 1);
	FILE* volatile file = NULL;
	char* volatile buffer = NULL;
	ParserNode** ppitem = NULL;
	ParserNode* pitem = NULL;
	int line = 0;

	try
	{
		buffer = throw_mem_alloc(4096);
		This->filename = throw_mem_allocstring(filename);
		This->bDayFirst = true;

		// Read every line in the file
		if ((file = fopen(filename, "rb")) == NULL)
			throw_string("FlErr0:%s", filename);

		Throwback_Start(filename);

		while(true)
		{
			if(fgets(buffer, 4095, file) == NULL)
				break;

			line++;
			String_StripBlanks(buffer);

			if (!strncmp(buffer, "Title:", 6))
			{
				String_StripBlanks(buffer + 6);
				This->title = throw_mem_allocstring(buffer + 6);
			}

			if (!strncmp(buffer, "Pre:", 4))
			{
				// Pre: "xx" "yy"
				char* x1, *x2, *x3, *x4;

				x1 = strchr(buffer, '\"');
				x2 = x1 ? strchr(x1 + 1, '\"') : NULL;
				x3 = x2 ? strchr(x2 + 1, '\"') : NULL;
				x4 = x3 ? strchr(x3 + 1, '\"') : NULL;
				if ((x4 == NULL)
				||  ((x4 - x3) != (x2 - x1))
				||  ((x2 - x1) == 1))
				{
					Throwback_Error(line, 0, "Incorrect Pre: section");
					throw_string("%s", "parsing error");
				}

				This->replace.pre.from = throw_mem_allocprint("%.*s", x2 - x1 - 1, x1 + 1);
				This->replace.pre.to = throw_mem_allocprint("%.*s", x4 - x3 - 1, x3 + 1);
			}
			else if (!strncmp(buffer, "Post:", 5))
			{
				// Pre: "xx" "yy"
				char* x1, *x2, *x3, *x4;

				x1 = strchr(buffer, '\"');
				x2 = x1 ? strchr(x1 + 1, '\"') : NULL;
				x3 = x2 ? strchr(x2 + 1, '\"') : NULL;
				x4 = x3 ? strchr(x3 + 1, '\"') : NULL;
				if ((x4 == NULL)
				||  ((x4 - x3) != (x2 - x1))
				||  ((x2 - x1) == 1))
				{
					Throwback_Error(line, 0, "Incorrect Post: section");
					throw_string("%s", "parsing error");
				}

				This->replace.post.from = throw_mem_allocprint("%.*s", x2 - x1 - 1, x1 + 1);
				This->replace.post.to = throw_mem_allocprint("%.*s", x4 - x3 - 1, x3 + 1);
			}
			else if (!strncmp(buffer, "Join:", 5))
			{
				String_StripBlanks(buffer + 5);
				This->replace.join = buffer[5];
			}
			else if (!strncmp(buffer, "Skip:", 5))
			{
				String_StripBlanks(buffer + 5);
				This->replace.skip = buffer[5];
			}
			else if (!strcmp(buffer, "Date:"))
			{
				String_StripBlanks(buffer + 5);
				This->bDayFirst = ((buffer[5] != 'M') || (buffer[5] != 'm'));
			}
			else if (!strcmp(buffer, "Path:"))
			{
				ppitem = &This->Paths;
				pitem = NULL;
			}
			else if (!strcmp(buffer, "Leaf:"))
			{
				ppitem = &This->Leafs;
				pitem = NULL;
			}
			else if (ppitem && (*buffer >= ' '))
			{
				*ppitem = pitem = New_ParserNode(pitem, line, buffer);
				ppitem = &pitem->pChild;
			}
		}

		Throwback_Stop();

		if (!feof(file))
			throw_last_oserror();

		fclose(file);
		mem_free(buffer);

		if ((This->Paths)
		&&  ((This->Paths->type != EParserNode_Type_Root) || This->Paths->pParent))
			throw_string("%s %s", filename, "paths corrupted");

		if ((This->Leafs)
		&&  ((This->Leafs->type != EParserNode_Type_Root) || This->Leafs->pParent))
			throw_string("%s %s", filename, "leafs corrupted");
	}
	catch
	{
		if (file) fclose(file);

		Throwback_Stop();

		mem_free(buffer);

		Delete_MetaScan(This);

		throw_current();
	}
	catch_end

	return This;
}

const char* MetaScan_GetName(const MetaScan* This)
{
	return This->filename;
}

/*----------------------------
 * MetaScan list
 */

static struct
{
	MetaScan* list[128];
	int       count;
	HMenu     menu;
} TheList = { {0}, 0, NULL};

static const char* pStdDir = "DigitalCD:StdRes.MetaScan";
static const char* pUserDir = "DigitalCDRes:MetaScan";

// Compare callback for qsort
static int compare_names(const void* pa, const void* pb)
{
	const MetaScan* const* pca = pa;
	const MetaScan* const* pcb = pb;

	return String_Collate(1, (*pca)->title, (*pcb)->title, -1);
}

void MetaScanList_Build(void)
{
	int     entry = 0;
	int     pathlen;
	int     stdcount = 0;
	char*   volatile pnewfile = NULL;
	char*   pbuffer;

	if (TheList.menu != NULL)
		return;

	// Scan folders
	try
	{
		pnewfile = throw_mem_alloc(MAXPATH);

		// check Standard versions
		pathlen = strlen(pStdDir) + 1;
		pbuffer = pnewfile + pathlen;

		strcpy(pnewfile, pStdDir);
		pbuffer[-1] = FILE_FOLDER_CHAR;

		while(entry != -1)
		{
			entry = Dir_ReadEntry(pStdDir, entry, pbuffer);

			if (pbuffer[0])
			{
				if (File_GetFileType(pnewfile) == FileType_Text)
				{
					MetaScan* pMeta = NULL;

					try
					{
						pMeta = New_MetaScan(pnewfile);
					}
					catch
					{
					}
					catch_end

					if (pMeta) TheList.list[TheList.count++] = pMeta;
				}
			}
		}

		stdcount = TheList.count;

		// sort std versions
		qsort(TheList.list, stdcount, sizeof(void*), compare_names);

		// check user versions
		pathlen = strlen(pUserDir) + 1;
		pbuffer = pnewfile + pathlen;

		strcpy(pnewfile, pUserDir);
		pbuffer[-1] = FILE_FOLDER_CHAR;
		entry = 0;

		while(entry != -1)
		{
			entry = Dir_ReadEntry(pUserDir, entry, pbuffer);

			if (pbuffer[0])
			{
				if (File_GetFileType(pnewfile) == FileType_Text)
				{
					MetaScan* pMeta = NULL;

					try
					{
						pMeta = New_MetaScan(pnewfile);
					}
					catch
					{
					}
					catch_end

					if (pMeta) TheList.list[TheList.count++] = pMeta;
				}
			}
		}

		// sort user versions
		qsort(TheList.list + stdcount, TheList.count - stdcount, sizeof(void*), compare_names);
	}
	catch
	{
		mem_free(pnewfile);

		throw_current();
	}
	catch_end

	mem_free(pnewfile);

	try
	{
		TheList.menu = throw_New_Menu_Empty(Msg_Lookup("LPrMenuT0"));
		throw_Menu_InsertItem(TheList.menu, "", -1);

		for (int i = 0; i < TheList.count; i++)
		{
			throw_Menu_InsertItem(TheList.menu, TheList.list[i]->title, -1);
		}

		if (stdcount < TheList.count)
			Menu_ItemSetProperty(TheList.menu, stdcount, EMenu_Item_Separator, true);
	}
	catch
	{
		if (TheList.menu)
		{
			Delete_Menu(TheList.menu, true);
			TheList.menu = NULL;
		}

		TheList.count = 0;

		throw_current();
	}
	catch_end
}

void MetaScanList_Release(void)
{
	if (TheList.menu)
	{
		Delete_Menu(TheList.menu, true);
		TheList.menu = NULL;
	}

	for (int i = 0; i < TheList.count; i++)
		Delete_MetaScan(TheList.list[i]);

	TheList.count = 0;
}

HMenu MetaScanList_GetMenu(void)
{
	return TheList.menu;
}

int MetaScanList_GetMenuIndex(const char* id)
{
	if (id == NULL) return 0;

	for (int i = 0; i < TheList.count; i++)
	{
		MetaScan* pMeta = TheList.list[i];

		if (!strcmp(id, pMeta->filename))
			return i + 1;
	}

	return 0;
}

MetaScan* MetaScanList_Find(const char* id)
{
	if (id == NULL) return NULL;

	for (int i = 0; i < TheList.count; i++)
	{
		MetaScan* pMeta = TheList.list[i];

		if (!strcmp(id, pMeta->filename))
			return pMeta;
	}

	return NULL;
}

MetaScan* MetaScanList_Get(int index)
{
	if (index == 0)
		return NULL;

	return TheList.list[index - 1];
}

/*----------------------------
 * Metadata extraction
 */

/**
 * Checks if current nodes works.
 */
static EParser_Eval MetaScan_Eval(MetaScan* This)
{
	NodeStack* pCur = This->pStackNode;

	switch(pCur->pNode->type)
	{
		case EParserNode_Type_Root:
		{
			pCur->bDone = false;

			return EParser_Eval_Ok;
		}
		break;
		case EParserNode_Type_End:
		{
			pCur->bDone = true;

			// Done here, follows with next item at parent level
			const ParserNode* pNode = pCur->pNode->pParent;

			while (pNode && pNode->type == EParserNode_Type_Root)
				pNode = pNode->pParent;

			if (pNode)
			{
				pCur[1].pNode = pNode->pNext;
				pCur[1].pEnd = pCur[1].pStart = pCur->pEnd;
				pCur[1].bDone = false;
				This->pStackNode++;

				return EParser_Eval_Stacked;
			}

			// No parent, not good if more to parse
			if (*pCur->pEnd)
				return EParser_Eval_Unstack;

			pCur->bDone = true;
			return EParser_Eval_Done;
		}
		break;
		case EParserNode_Type_Optional:
		case EParserNode_Type_Alternative:
		{
			pCur->bDone = true;

			// Follows with first child
			pCur[1].pNode = pCur->pNode->pChild;
			pCur[1].pEnd = pCur[1].pStart = pCur->pEnd;
			pCur[1].bDone = false;
			This->pStackNode++;

			return EParser_Eval_Stacked;
		}
		break;
		case EParserNode_Type_Space:
		{
			if (isspace(*pCur->pEnd))
			{
				pCur->pEnd++;
				return EParser_Eval_Ok;
			}

			return EParser_Eval_Unstack;
		}
		break;
		case EParserNode_Type_Comp:
		{
			// Move to next node
			pCur[1].pNode = pCur->pNode->pNext;
			pCur[1].pEnd = pCur[1].pStart = pCur->pEnd;
			pCur[1].bDone = false;
			This->pStackNode++;

			return EParser_Eval_Stacked;
		}
		break;
	}

	// For remaining types, skip leading blanks
	if (pCur->pEnd == pCur->pStart)
	{
		while (isspace(*pCur->pEnd))
			pCur->pEnd++;
		pCur->pStart = pCur->pEnd;
	}

	switch(pCur->pNode->type)
	{
		case EParserNode_Type_Constant:
		{
			if (!String_Collate(1, pCur->pEnd, pCur->pNode->pText, pCur->pNode->len))
			{
				pCur->pEnd += pCur->pNode->len;
				pCur->bDone = true;

				pCur[1].pNode = pCur->pNode->pNext;
				pCur[1].pEnd = pCur[1].pStart = pCur->pEnd;
				pCur[1].bDone = false;
				This->pStackNode++;

				return EParser_Eval_Stacked;
			}

			return EParser_Eval_Unstack;
		}
		break;
		case EParserNode_Type_Variable:
		{
			if (*pCur->pEnd)
			{
				pCur->pEnd++;
				return EParser_Eval_Ok;
			}
		}
		break;
		case EParserNode_Type_VarLeaf:
		{
			if (*pCur->pEnd)
			{
				if (*pCur->pEnd == FILE_FOLDER_CHAR)
					return EParser_Eval_Unstack;

				pCur->pEnd++;
				return EParser_Eval_Ok;
			}
		}
		break;
		case EParserNode_Type_VarInt:
		{
			if (*pCur->pEnd)
			{
				if ((*pCur->pEnd >= '0')
				&&  (*pCur->pEnd <= '9'))
				{
					pCur->pEnd++;
					return EParser_Eval_Ok;
				}

				return EParser_Eval_Unstack;
			}
		}
		break;
		case EParserNode_Type_VarDate:
		{
			char sep = 0;
			int len = 0;
			int year = -1;
			int month = -1;
			int day = -1;

			// Evaluate all in one go
			if (pCur->pEnd != pCur->pStart)
				return EParser_Eval_Unstack;

			pCur->flags = 0;

			// Find first seperator
			for (len = 0; len < 5; len++)
			{
				if ((pCur->pEnd[len] < '0')
				||  (pCur->pEnd[len] > '9'))
					break;
			}

			if ((len == 0)
			||  (len == 3)
			||  (len == 5))
				return EParser_Eval_Unstack;

			sep = pCur->pEnd[len];
			if ((sep != '-')
			&&  (sep != '/')
			&&  (sep != '.'))
				sep = 0;

			if (len == 4)
			{
				year = (pCur->pEnd[0] - '0') * 1000
				     + (pCur->pEnd[1] - '0') * 100
				     + (pCur->pEnd[2] - '0') * 10
				     + (pCur->pEnd[3] - '0');
			}
			else
			{
				if (!sep) return EParser_Eval_Unstack;
				if (len == 2)
				{
					day = (pCur->pEnd[0] - '0') * 10
					    + (pCur->pEnd[1] - '0');
				}
				else
				{
					day = (pCur->pEnd[0] - '0');
				}
			}
			pCur->pEnd += len;

			if (sep)
			{
				pCur->pEnd++;
				// Find second seperator
				for (len = 0; len < 5; len++)
				{
					if ((pCur->pEnd[len] < '0')
					||  (pCur->pEnd[len] > '9'))
						break;
				}

				if ((len == 0)
				||  (len == 3)
				||  (len == 5))
					return EParser_Eval_Unstack;

				if (len == 4)
				{
					if (year >= 0) return EParser_Eval_Unstack;
					year = (pCur->pEnd[0] - '0') * 1000
					     + (pCur->pEnd[1] - '0') * 100
					     + (pCur->pEnd[2] - '0') * 10
				    	 + (pCur->pEnd[3] - '0');
					month = day;
					day = -2;
				}
				else if (len == 2)
				{
					month = (pCur->pEnd[0] - '0') * 10
					      + (pCur->pEnd[1] - '0');
				}
				else
				{
					month = (pCur->pEnd[0] - '0');
				}

				pCur->pEnd += len;
			}

			if (pCur->pEnd[0] == sep)
			{
				pCur->pEnd++;
				// Find end date
				for (len = 0; len < 5; len++)
				{
					if ((pCur->pEnd[len] < '0')
					||  (pCur->pEnd[len] > '9'))
						break;
				}

				if ((len == 0)
				||  (len == 3)
				||  (len == 5))
					return EParser_Eval_Unstack;

				if (pCur->pEnd[len] == sep)
					return EParser_Eval_Unstack;

				if (len == 4)
				{
					if (year >= 0) return EParser_Eval_Unstack;
					year = (pCur->pEnd[0] - '0') * 1000
					     + (pCur->pEnd[1] - '0') * 100
					     + (pCur->pEnd[2] - '0') * 10
				    	 + (pCur->pEnd[3] - '0');
				}
				else
				{
					if (day != -1) return EParser_Eval_Unstack;
					if (len == 2)
					{
						day = (pCur->pEnd[0] - '0') * 10
						    + (pCur->pEnd[1] - '0');
					}
					else
					{
						day = (pCur->pEnd[0] - '0');
					}
				}

				pCur->pEnd += len;
			}

			// Eval year
			if ((year < 1000)
			||  (year > 2999))
				return EParser_Eval_Unstack;

			// Eval month
			if (month >= 0)
			{
				if ((month < 1)
				||  (month > 12))
					return EParser_Eval_Unstack;
			}

			// Eval day
			if (day >= 0)
			{
				if ((day < 1)
				||  (day > 31))
					return EParser_Eval_Unstack;
			}

			pCur->flags = year;
			if (This->bDayFirst)
			{
				if (month >= 0) pCur->flags |= month << 16;
				if (day >= 0) pCur->flags |= day << 24;
			}
			else
			{
				if (day >= 0) pCur->flags |= day << 16;
				if (month >= 0) pCur->flags |= month << 24;
			}
		}
		break;
		case EParserNode_Type_VarExt:
		{
			if (*pCur->pEnd)
			{
				if ((*pCur->pEnd == FILE_EXTENSION_CHAR)
				||  (*pCur->pEnd == FILE_FOLDER_CHAR))
					return EParser_Eval_Unstack;

				pCur->pEnd++;
				return EParser_Eval_Ok;
			}
		}
		break;
	}

	// Common to all variable, when reached end of text to parse
	if (pCur->pEnd == pCur->pStart)
		return EParser_Eval_Unstack;

	pCur->bDone = true;

	// More nodes may follow, so we must check them too
	pCur[1].pNode = pCur->pNode->pNext;
	pCur[1].pEnd = pCur[1].pStart = pCur->pEnd;
	pCur[1].bDone = false;
	This->pStackNode++;

	return EParser_Eval_Stacked;
}

static bool MetaScan_ScanText(MetaScan* This, FLTrack* pTrack, ParserNode* pRules, char* string)
{
	char* pc;
	EParser_Eval val = EParser_Eval_Stacked;
	NodeStack* pNS;
	bool bChanged = false;

	// May not have any rules
	if (!pRules) return false;

	// Preprocessing
	String_Translate(string, This->replace.pre.from, This->replace.pre.to);

	// Skips?
	pc = string;
	while ((pc = strchr(pc + 1, This->replace.skip)) != NULL)
	{
		if (pc[-1] == FILE_FOLDER_CHAR)
		{
			char* pcd = strchr(pc, FILE_FOLDER_CHAR);

			if (pcd)
				memmove(pc - 1, pcd, strlen(pcd) + 1);
			else
			{
				pc[-1] = 0;
				break;
			}
		}
	}

	// Joins?
	pc = string + 1;
	while ((pc = strchr(pc + 1, This->replace.join)) != NULL)
	{
		if (pc[-1] == FILE_FOLDER_CHAR)
		{
			pc[-1] = ' ';
			memmove(pc, pc + 1, strlen(pc));
		}
	}

	pNS= &This->Stack[0];
	pNS->pNode = pRules;
	pNS->pEnd = pNS->pStart = string;
	pNS->bDone = false;
	This->pStackNode = pNS;

	if (pRules->type != EParserNode_Type_Root)
		throw_string("assert");
	if (pRules->pParent)
		throw_string("assert");

	while(val != EParser_Eval_Done)
	{
		pNS = This->pStackNode;
		if (pNS < &This->Stack[0])
			throw_string("assert");
		if (pNS >= &This->Stack[50])
			throw_string("assert");

// Log("Val: %d %s (%s) [%s]\n", val, ParserNode_TypeTxt[pNS->pNode->type], pNS->pNode->pText, pNS->pEnd);
		switch (val)
		{
			case EParser_Eval_Stacked:
			{
				// Eval newly stacked item
				val = MetaScan_Eval(This);
			}
			break;
			case EParser_Eval_Ok:
			{
				// Try to stack next item
				pNS = This->pStackNode + 1;
				pNS->pNode = This->pStackNode->pNode->pNext;
				pNS->pEnd = pNS->pStart = This->pStackNode->pEnd;
				pNS->bDone = false;
				This->pStackNode = pNS;
				val = EParser_Eval_Stacked;
			}
			break;
			case EParser_Eval_Unstack:
			{
				// Unstack back to unfinished node (such as a Root node)
				pNS = This->pStackNode - 1;
				while (pNS->bDone == true)
					pNS--;

				if (pNS < &This->Stack[0])
					throw_string("assert");

				if (pNS->pNode->type != EParserNode_Type_Root)
				{
					// Reavaluate this one
					This->pStackNode = pNS;
					val = MetaScan_Eval(This);
				}
				else if (pNS->pNode->pChild != NULL)
				{
					// Try an alternative when one exists
					pNS->pEnd = pNS->pStart;
					pNS->pNode = pNS->pNode->pChild;
					pNS->bDone = false;
					This->pStackNode = pNS;
					val = EParser_Eval_Stacked;
				}
				else if (pNS > &This->Stack[0])
				{
					// No alternative, move back to parent container
					pNS--;

					if (pNS->pNode->type == EParserNode_Type_Optional)
					{
						// Skip optional part and retry
						pNS->pEnd = pNS->pStart;
						pNS->pNode = pNS->pNode->pNext;
						pNS->bDone = false;
						This->pStackNode = pNS;
						val = EParser_Eval_Stacked;
					}
					else if (pNS->pNode->type == EParserNode_Type_Alternative)
					{
						// Parent failed, keep unstacking
						This->pStackNode = pNS;
						val = EParser_Eval_Unstack;
					}
					else
					{
						throw_string("assert");
					}
				}
				else
				{
					if (pNS < &This->Stack[0])
						throw_string("assert");

					// No alternative, nothing else on stack, we tried everything
					This->pStackNode = pNS--;
					val = EParser_Eval_Done;
				}
			}
			break;
		}
	}

	for (pNS = This->pStackNode - 1; pNS >= &This->Stack[0]; pNS--)
	{
		*pNS->pEnd = 0;

		if (pNS->pNode->type >= EParserNode_Type_Variable)
		{
			const VarName* pVar;

			for (pVar = &VarList[0]; pVar->pName; pVar++)
			{
				if (!strcmp(pNS->pNode->pText, pVar->pName))
				{
					switch(pVar->id)
					{
						case EMetaId_None:
						{
							// Ignore
						}
						break;
						case EMetaId_StreamOrder:
						{
							bChanged |= FLTrack_SetOrder(pTrack, EMetaOrigin_FilePath, atoi(pNS->pStart));
						}
						break;
						case EMetaId_StreamDate:
						{
							char date[11];

							if (pNS->flags >> 24)
								snprintf(date, sizeof(date), "%04d-%02d-%02d", pNS->flags & 0xffff, (pNS->flags >> 16) & 0xff, pNS->flags >> 24);
							else if (pNS->flags >> 16)
								snprintf(date, sizeof(date), "%04d-%02d", pNS->flags & 0xffff, pNS->flags >> 16);
							else
								snprintf(date, sizeof(date), "%04d", pNS->flags);

							bChanged |= FLTrack_SetMetaText
							               ( pTrack
							               , pVar->id
							               , EMetaOrigin_FilePath
							               , date
							               );
						}
						break;
						default:
						{
							// Postprocessing
							String_Translate
								( pNS->pStart
								, This->replace.post.from
								, This->replace.post.to
								);
							String_StripBlanks(pNS->pStart);

							bChanged |= FLTrack_SetMetaText
							               ( pTrack
							               , pVar->id
							               , EMetaOrigin_FilePath
							               , pNS->pStart
							               );
						}
					}

					break;
				}
			}
		}
		else if (pNS->pNode->type == EParserNode_Type_Comp)
		{
			bChanged |= FLTrack_SetFlags(pTrack, FLTrack_Compilation, FLTrack_Compilation);
		}
	}

	return bChanged;
}

bool MetaScan_Scan(MetaScan* This, FLTrack* pTrack, const char* cpath, const char* cleaf)
{
	bool bChanged = false;

	char* string = throw_mem_allocstring(cpath);
	int len = strlen(string);
	// Remove seperator at end of path
	if ((len > 0) && (string[len - 1] == FILE_FOLDER_CHAR))
		string[len - 1] = 0;
	bChanged |= MetaScan_ScanText(This, pTrack, This->Paths, string);
	mem_free(string);
	string = throw_mem_allocstring(cleaf);
	bChanged |= MetaScan_ScanText(This, pTrack, This->Leafs, string);
	mem_free(string);

	return bChanged;
}
