#include <dos/dosextens.h>
#include <dos/rdargs.h>

#include <exec/memory.h>

#include <clib/alib_protos.h>
#include <clib/utility_protos.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>

#include <pragmas/exec_sysbase_pragmas.h>
#include <pragmas/utility_pragmas.h>
#include <pragmas/dos_pragmas.h>

#include <string.h>
#include <stdarg.h>

struct TreeNode
{
	struct Node		Node;
	struct TreeNode *	Parent;
	struct DateStamp	Date;
	LONG			Size;

	struct List		Branches;
	LONG			Entries;
	LONG			Counter;
};

struct AlphaTreeNode
{
	struct Node		Node;
	struct TreeNode *	Link;
};

extern struct ExecBase *	SysBase;
extern struct DosLibrary *	DOSBase;
struct Library *		UtilityBase;

struct List			Tree;
APTR				Pool;
UBYTE				GlobalBuffer[1024];
LONG				Counter;
UBYTE				NodeBuffer[80];

struct List			AlphaList;
struct AlphaTreeNode *		AlphaTable[26];

VOID
SPrintf(STRPTR Buffer,STRPTR FormatString,...)
{
	va_list VarArgs;

	va_start(VarArgs,FormatString);
	RawDoFmt(FormatString,VarArgs,(VOID (*)())"\x16\xC0\x4E\x75",Buffer);
	va_end(VarArgs);
}

STRPTR
GetNodeName(struct TreeNode *Node)
{
	UBYTE Tail[80];
	LONG TailLen,Len;

	SPrintf(Tail,".%05ld.guide",Node->Counter);

	strcpy(NodeBuffer,Node->Node.ln_Name);

	strcat(NodeBuffer,"________________________________");

	TailLen = strlen(Tail);

	Len = strlen(NodeBuffer);

	strcpy(&NodeBuffer[30 - TailLen],Tail);
/*
	if(Len + TailLen > 30)
		strcpy(&NodeBuffer[30 - TailLen],Tail);
	else
		strcat(NodeBuffer,Tail);
*/
	return(NodeBuffer);
}

VOID
InsertSorted(struct List *List,struct Node *Node)
{
    struct Node *Other,*Last;

    for(Other = List->lh_Head, Last = NULL ; Other->ln_Succ ; Last = Other, Other = Other->ln_Succ)
    {
        if(Stricmp(Other->ln_Name,Node->ln_Name) > 0)
        {
            Insert(List,Node,Last);
            return;
        }
    }

    AddTail(List,Node);
}

struct TreeNode *
CreateNode(struct TreeNode *Parent,STRPTR Name,struct DateStamp *Date,LONG Size)
{
	struct TreeNode *Node;

	if(Node = (struct TreeNode *)AllocPooled(Pool,sizeof(struct TreeNode) + strlen(Name) + 1))
	{
		struct AlphaTreeNode *Link;

		Node->Node.ln_Name = (char *)(Node + 1);

		strcpy(Node->Node.ln_Name,Name);

		Node->Parent = Parent;
		CopyMem(Date,&Node->Date,sizeof(struct DateStamp));

		Node->Size = Size;
		NewList(&Node->Branches);
		Node->Entries = 0;

		if(Link = AllocPooled(Pool,sizeof(*Link)))
		{
			STRPTR Stuff;

			Stuff = Node->Node.ln_Name;

			while(*Stuff == ' ')
				Stuff++;

			Link->Node.ln_Name = Stuff;
			Link->Link = Node;

			InsertSorted(&AlphaList,(struct Node *)Link);

			return(Node);
		}
	}

	SetIoErr(ERROR_NO_FREE_STORE);

	return(NULL);
}

VOID
CloseAll(VOID)
{
	if(Pool)
		DeletePool(Pool);

	if(UtilityBase)
		CloseLibrary(UtilityBase);
}

BOOL
OpenAll(VOID)
{
	if(UtilityBase = OpenLibrary("utility.library",37))
	{
		if(Pool = CreatePool(MEMF_ANY,4096,4096))
		{
			NewList(&Tree);
			NewList(&AlphaList);

			return(TRUE);
		}
	}

	return(FALSE);
}

VOID
Indent(LONG Level)
{
	UBYTE LocalBuffer[256];

	if(Level > 255)
		Level = 255;

	memset(LocalBuffer,' ',Level);

	LocalBuffer[Level] = 0;

	Printf(LocalBuffer);
}

LONG
Scan(struct TreeNode *Parent,BPTR DirLock,LONG Level)
{
	struct FileInfoBlock __aligned FileInfo;
	LONG Error;
	struct List *List;
	LONG Len;

	if(Parent)
		List = &Parent->Branches;
	else
		List = &Tree;

	if(Examine(DirLock,&FileInfo))
	{
		struct TreeNode *Node;

		while(ExNext(DirLock,&FileInfo))
		{
			Len = strlen(FileInfo.fib_FileName);

			if(Len >= strlen(".info") && !Stricmp(&FileInfo.fib_FileName[Len - strlen(".info")],".info"))
				continue;

			Indent(Level);
			Printf("%s %s\n",FileInfo.fib_FileName,(FileInfo.fib_DirEntryType > 0) ? "(Dir)" : "");

			if(Node = CreateNode(Parent,FileInfo.fib_FileName,&FileInfo.fib_Date,(FileInfo.fib_DirEntryType > 0) ? -1 : FileInfo.fib_Size))
			{
				InsertSorted(List,&Node->Node);

				if(FileInfo.fib_DirEntryType > 0)
				{
					BPTR DirLock;

					if(DirLock = Lock(FileInfo.fib_FileName,SHARED_LOCK))
					{
						BPTR OldDir;

						OldDir = CurrentDir(DirLock);

						Error = Scan(Node,DirLock,Level + 1);

						CurrentDir(OldDir);
						UnLock(DirLock);

						if(Error)
						{
							SetIoErr(Error);
							break;
						}
					}
					else
						break;
				}

				Printf("\033[A\033[K");
				Flush(Output());
			}
			else
				break;
		}

		Error = IoErr();

		if(Error == ERROR_NO_MORE_ENTRIES)
			Error = 0;
	}
	else
		Error = IoErr();

	return(Error);
}

VOID
UpdateCounter(struct TreeNode *Parent)
{
	do
	{
		Parent->Entries++;

		Parent = Parent->Parent;
	}
	while(Parent);
}

VOID
UpdateTree(struct List *List)
{
	struct TreeNode *Node;

	for(Node = (struct TreeNode *)List->lh_Head ; Node->Node.ln_Succ ; Node = (struct TreeNode *)Node->Node.ln_Succ)
	{
		if(Node->Parent)
			UpdateCounter(Node->Parent);

		if(Node->Size < 0)
		{
			UpdateTree(&Node->Branches);

			Node->Counter = ++Counter;
		}
	}
}

BOOL
BuildName(struct TreeNode *Node,STRPTR Name)
{
	LONG NameLen,Len,i;

	NameLen = strlen(Node->Node.ln_Name);

	if(NameLen > 1024)
		return(FALSE);

	strcpy(Name,Node->Node.ln_Name);

	while(Node->Parent)
	{
		Node = Node->Parent;

		Len = strlen(Node->Node.ln_Name);

		if(NameLen + Len + 1 > 1024)
			return(FALSE);

		for(i = 0 ; i < NameLen ; i++)
			Name[Len + 1 + NameLen - 1 - i] = Name[NameLen - 1 - i];

		CopyMem(Node->Node.ln_Name,Name,Len);

		Name[Len] = '/';

		NameLen += Len + 1;
		Name[NameLen] = 0;
	}

	return(TRUE);
}

VOID
DumpTree(struct List *List,LONG Level)
{
	struct TreeNode *Node;

	for(Node = (struct TreeNode *)List->lh_Head ; Node->Node.ln_Succ ; Node = (struct TreeNode *)Node->Node.ln_Succ)
	{
		if(SetSignal(0,0) & SIGBREAKF_CTRL_C)
			break;

		BuildName(Node,GlobalBuffer);
		Printf("%s\n",GlobalBuffer);
/*
		Indent(Level);

		Printf(Node->Node.ln_Name);

		if(Node->Size < 0)
			Printf(" (DIR: %ld entries)\n",Node->Entries);
		else
			Printf("\n");
*/
		if(Node->Size < 0)
			DumpTree(&Node->Branches,Level + 1);
	}
}

VOID
DumpDate(BPTR FileHandle,struct DateStamp *Date)
{
	struct DateTime DateTime;

	UBYTE DateString[40];
	UBYTE TimeString[40];

	CopyMem(Date,&DateTime.dat_Stamp,sizeof(struct DateStamp));

	DateTime.dat_Format	= FORMAT_DOS;
	DateTime.dat_Flags	= NULL;
	DateTime.dat_StrDay	= NULL;
	DateTime.dat_StrDate	= DateString;
	DateTime.dat_StrTime	= TimeString;

	DateToStr(&DateTime);
	FPrintf(FileHandle,"%-9.9s %s\n",DateString,TimeString);
}

VOID
GuideTree(STRPTR RootNode,struct List *List,struct TreeNode *Parent,STRPTR Name,LONG Level)
{
	struct TreeNode *Node;
	UBYTE LocalBuffer[60];
	BPTR FileHandle;

	if(Level == 0)
	{
		SPrintf(LocalBuffer,"%s",RootNode);

		if(FileHandle = Open(LocalBuffer,MODE_NEWFILE))
		{
			strcpy(LocalBuffer,"main");

			FPrintf(FileHandle,"@database \"%s\"\n\n",RootNode);
		}
		else
			return;
	}
	else
	{
		SPrintf(LocalBuffer,"Guide/%s",GetNodeName(Parent));

		if(FileHandle = Open(LocalBuffer,MODE_NEWFILE))
		{
			strcpy(LocalBuffer,"main");

			FPrintf(FileHandle,"@database \"%s\"\n",GetNodeName(Parent));
			FPrintf(FileHandle,"@index \"%s/main\"\n\n",RootNode);
		}
		else
			return;
	}

	FPrintf(FileHandle,"@node %s \"%s\"\n",LocalBuffer,Name);

	for(Node = (struct TreeNode *)List->lh_Head ; Node->Node.ln_Succ ; Node = (struct TreeNode *)Node->Node.ln_Succ)
	{
		FPrintf(FileHandle,"@{\" %-32.32s \" link ",Node->Node.ln_Name);

		if(Node->Size < 0)
			FPrintf(FileHandle,"\"Guide/%s/main\"}",GetNodeName(Node));
		else
		{
			BuildName(Node,GlobalBuffer);

			FPrintf(FileHandle,"\"%s/main\"}",GlobalBuffer);
		}

		if(Node->Size < 0)
			FPrintf(FileHandle," (DIR) %8ld Entr%s ",Node->Entries,Node->Entries == 1 ? "y  " : "ies");
		else
			FPrintf(FileHandle,"       %8ld Byte%s   ",Node->Size,Node->Size == 1 ? " " : "s");

		DumpDate(FileHandle,&Node->Date);
	}
/*
	if(Level > 0)
	{
		if(Level == 1)
			FPrintf(FileHandle,"\n@{\" Root Directory \" link \"main\"} @{\" Parent Directory \" link \"main\"}\n");
		else
			FPrintf(FileHandle,"\n@{\" Root Directory \" link \"main\"} @{\" Parent Directory \" link \"%s.%08lx\"}\n",Parent->Parent->Node.ln_Name,Parent->Parent);
	}
*/
	FPrintf(FileHandle,"@endnode\n\n");

	Close(FileHandle);

	for(Node = (struct TreeNode *)List->lh_Head ; Node->Node.ln_Succ ; Node = (struct TreeNode *)Node->Node.ln_Succ)
	{
		if(Node->Size < 0)
		{
			BuildName(Node,GlobalBuffer);

			GuideTree(RootNode,&Node->Branches,Node,GlobalBuffer,Level + 1);
		}
	}
}

VOID
GuideAlphaTree(STRPTR RootName,struct List *List)
{
	UBYTE LocalBuffer[256];
	UBYTE Left[40],Right[40];
	BPTR FileHandle;

	strcpy(LocalBuffer,"Alpha.guide");

	if(FileHandle = Open(LocalBuffer,MODE_NEWFILE))
	{
		struct AlphaTreeNode *First,*Last,*This;
		LONG i;

		FPrintf(FileHandle,"@database \"%s\"\n\n",FilePart(LocalBuffer));
		FPrintf(FileHandle,"@node main \"Alphabetical index\"\n");

		for(i = 0, Last = NULL ; i < 26 ; i++)
		{
			if(Last == NULL)
			{
				Last = (struct AlphaTreeNode *)List->lh_Head;
				AlphaTable[i] = Last;
			}

			while(Last->Node.ln_Succ && ToUpper(Last->Node.ln_Name[0]) <= i + 'A')
				Last = (struct AlphaTreeNode *)Last->Node.ln_Succ;

			if(Last->Node.ln_Succ && i < 26)
			{
				LONG Index;

				Index = ToUpper(Last->Node.ln_Name[0]) - 'A';

				if(Index <= 25)
					AlphaTable[Index] = Last;
			}

			if(!Last->Node.ln_Succ)
				break;
		}

		for(i = 0 ; i < 26 ; i++)
		{
			if(AlphaTable[i])
			{
				LONG j;

				First	= AlphaTable[i];
				Last	= NULL;

				for(j = i + 1 ; j < 26 ; j++)
				{
					if(AlphaTable[j])
					{
						Last = (struct AlphaTreeNode *)AlphaTable[j]->Node.ln_Pred;
						break;
					}
				}

				if(!Last)
					Last = (struct AlphaTreeNode *)List->lh_TailPred;

				if(First && Last)
				{
					struct AlphaTreeNode *Node;
					LONG Count;

					Count = 0;
					Node = First;

					while(Node->Node.ln_Succ)
					{
						Count++;

						if(Node == Last)
							break;
						else
							Node = (struct AlphaTreeNode *)Node->Node.ln_Succ;
					}

//					FPrintf(FileHandle,"@{\" %-32.32s \" link \"Guide/Index_%lc.guide/main\"} %8ld Entries\n",First->Node.ln_Name,i + 'A',Count);

					SPrintf(Left,"`%s'",First->Node.ln_Name);
					SPrintf(Right,"`%s'",Last->Node.ln_Name);

					if(First == Last)
						FPrintf(FileHandle,"@{\" %-34.34s    %-34.34s \" link \"Guide/Index_%lc.guide/main\"} %8ld Entr%s\n",Left," ",i + 'A',Count,Count == 1 ? "y" : "ies");
					else
						FPrintf(FileHandle,"@{\" %-34.34s to %-34.34s \" link \"Guide/Index_%lc.guide/main\"} %8ld Entr%s\n",Left,Right,i + 'A',Count,Count == 1 ? "y" : "ies");
				}
			}
		}

		FPrintf(FileHandle,"@endnode\n");

		Close(FileHandle);

		for(i = 0 ; i < 26 ; i++)
		{
			if(AlphaTable[i])
			{
				LONG j;

				First	= AlphaTable[i];
				Last	= NULL;

				for(j = i + 1 ; j < 26 ; j++)
				{
					if(AlphaTable[j])
					{
						Last = (struct AlphaTreeNode *)AlphaTable[j]->Node.ln_Pred;
						break;
					}
				}

				if(!Last)
					Last = (struct AlphaTreeNode *)List->lh_TailPred;

				if(First && Last)
				{
					SPrintf(LocalBuffer,"Guide/Index_%lc.guide",i + 'A');

					if(FileHandle = Open(LocalBuffer,MODE_NEWFILE))
					{
						struct TreeNode *Which;
						This = First;

						FPrintf(FileHandle,"@database %s\n\n@node main \"Index\"\n",FilePart(LocalBuffer));

						while(This->Node.ln_Succ)
						{
							if(This->Link->Size < 0)
								Which = This->Link;
							else
								Which = This->Link->Parent;

							if(Which)
								FPrintf(FileHandle,"@{\" %-32.32s \" link \"Guide/%s/main\"}",This->Node.ln_Name,GetNodeName(Which));
							else
								FPrintf(FileHandle,"@{\" %-32.32s \" link \"%s/main\"}",This->Node.ln_Name,RootName);

							if(This->Link->Size < 0)
							{
								FPrintf(FileHandle," (DIR) %8ld Entr%s ",This->Link->Entries,This->Link->Entries == 1 ? "y  " : "ies");
							}
							else
							{
								FPrintf(FileHandle,"       %8ld Byte%s   ",This->Link->Size,This->Link->Size == 1 ? " " : "s");
							}

							DumpDate(FileHandle,&This->Link->Date);

							if(This == Last)
								break;
							else
								This = (struct AlphaTreeNode *)This->Node.ln_Succ;

							if(SetSignal(0,0) & SIGBREAKF_CTRL_C)
								break;
						}

						FPrintf(FileHandle,"\nPlease note that links to files will lead to the drawers\nthe respective files are found in.\n");

						FPrintf(FileHandle,"@endnode\n");

						Close(FileHandle);
					}
				}
			}

			if(CheckSignal(SIGBREAKF_CTRL_C))
				break;
		}

	}
}

int
main(int argc,char **argv)
{
	int rc = RETURN_FAIL;

	if(OpenAll())
	{
		struct RDArgs *Args;
		struct { STRPTR Dir; STRPTR To; } Params;

		memset(&Params,0,sizeof(Params));

		if(Args = ReadArgs("DIR/A,TO/A",(LONG *)&Params,NULL))
		{
			BPTR DirLock;

			rc = RETURN_ERROR;

			if(DirLock = Lock(Params.Dir,SHARED_LOCK))
			{
				struct FileInfoBlock __aligned FileInfo;

				if(Examine(DirLock,&FileInfo))
				{
					if(FileInfo.fib_DirEntryType > 0)
					{
						LONG Error;
						BPTR OldDir;

						OldDir = CurrentDir(DirLock);

						Error = Scan(NULL,DirLock,0);

						CurrentDir(OldDir);

						if(Error)
							PrintFault(Error,"");
						else
						{
							Printf("Updating... ");
							Flush(Output());

							UpdateTree(&Tree);

							Printf("Dumping... ");
							Flush(Output());

							UnLock(CreateDir("Guide"));

							GuideTree(FilePart(Params.To),&Tree,NULL,FileInfo.fib_FileName,0);

							Printf("Dumping... ");
							Flush(Output());

							GuideAlphaTree(FilePart(Params.To),&AlphaList);

							Printf("done.\n");

							rc = RETURN_OK;
						}
					}
					else
						PrintFault(ERROR_OBJECT_WRONG_TYPE,Params.Dir);
				}
				else
					PrintFault(IoErr(),Params.Dir);

				UnLock(DirLock);
			}
			else
				PrintFault(IoErr(),Params.Dir);

			FreeArgs(Args);
		}
		else
		{
			PrintFault(IoErr(),"TreeGuide");
			rc = RETURN_ERROR;
		}
	}

	CloseAll();

	return(rc);
}
