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

#include <vcl.h>
#pragma hdrstop

#include "MainForm.h"
#include <algorithm>
#include <ctime>

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TfrmMain *frmMain;
//---------------------------------------------------------------------------

/*
	Constructor
*/
__fastcall TfrmMain::TfrmMain(TComponent* Owner) : TForm(Owner)
{
	AbortProcess = false;
	Randomize();
}

/*
	Updates the display of the number of nodes in the tree.
*/
void TfrmMain::UpdateCount(void)
{
	lblNodeCount->Caption = IntToStr(treeWords->Items->Count);
}

/*
	Update the node information display
*/
void TfrmMain::UpdateNodeInfo(TTreeNode *node)
{
	if (!node)
	{
		lblText->Caption = "";
		lblIndex->Caption = "";
		lblLevel->Caption = "";
		lblParent->Caption = "";
	}
	else
	{
		lblText->Caption = node->Text;
		lblIndex->Caption = IntToStr(node->Index);
		lblLevel->Caption = IntToStr(node->Level);
		if (node->Parent)
			lblParent->Caption = node->Parent->Text;
		else
			lblParent->Caption = "";
	}
}

/*
	Checks to see if Ancestor is an ancestor of Child.
*/
bool TfrmMain::IsAncestorNode(TTreeNode *Ancestor, TTreeNode *Child) const
{
	if (!Child || !Ancestor)
		return false;

	TTreeNode *p = Child->Parent;

		// Walk up the tree to see if Parent is an ancestor of Child
	while (p)
	{
		if (p == Ancestor)
			return true;
		p = p->Parent;
	}
	return false;
}

/*
	Moves a node (subtree) to be a child of another node
*/
void TfrmMain::MoveTreeNode(TTreeView *tv, TTreeNode *SourceNode, TTreeNode *DestNode)
{
		// Copy the subtree to the destination
	CopyTreeNodesRec(tv, SourceNode, SourceNode, DestNode);

		// Delete the original sub-tree
	SourceNode->Delete();
}

/*
	Recursive function that makes a copy of a subtree.
*/
void TfrmMain::CopyTreeNodesRec(TTreeView *tv, TTreeNode *OriginalNode,  TTreeNode *SourceNode,  TTreeNode *DestNode)
{
		// If either is NULL, nothing to do
	if (!DestNode || !SourceNode)
		return;

		// Add a new child and copy the properties and data
	TTreeNode *NewNode = tv->Items->AddChild(DestNode, SourceNode->Text);
	NewNode->ImageIndex = SourceNode->ImageIndex;
	NewNode->StateIndex = SourceNode->StateIndex;
	NewNode->SelectedIndex = SourceNode->SelectedIndex;
	NewNode->Data = SourceNode->Data;  // if there is any data being used

		// If there are children, copy them first, recursively (depth-first)
	if (SourceNode->HasChildren)
		CopyTreeNodesRec(tv, OriginalNode, SourceNode->getFirstChild(), NewNode);

		// Copy all siblings, unless at original level (breadth-first)
	if (OriginalNode != SourceNode)
		CopyTreeNodesRec(tv, OriginalNode, SourceNode->getNextSibling(), DestNode);
}

//******************************************************************************
//******************************************************************************
// Event handlers
//******************************************************************************
void __fastcall TfrmMain::FormCreate(TObject *Sender)
{
	treeWords->Align = alClient;
	pnlRight->DoubleBuffered = true;
	pnlRight->BevelOuter = bvNone;
	UpdateNodeInfo(0);

		// Enable files to be dropped onto the form (a drop target)
	DragAcceptFiles(this->Handle, true);
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::FormClose(TObject *Sender, TCloseAction &Action)
{
	ClearNodes(); // Need to delete the Data
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::FormDestroy(TObject *Sender)
{
		// Enable files to be dropped onto the form (a drop target)
	DragAcceptFiles(this->Handle, false);
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::btnAddTopLevelNodeClick(TObject *Sender)
{
	treeWords->Items->Add(NULL, edtWordToAdd->Text);
	UpdateCount();
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::btnAddChildNodeClick(TObject *Sender)
{
	TTreeNode *tn = treeWords->Selected;
	treeWords->Items->AddChild(tn, edtWordToAdd->Text);
	UpdateCount();
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::btnExpandAllClick(TObject *Sender)
{
	treeWords->Items->BeginUpdate();
	treeWords->FullExpand(); // Expand entire tree
	treeWords->Items->EndUpdate();
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::btnCollapseAllClick(TObject *Sender)
{
	treeWords->Items->BeginUpdate();
	treeWords->FullCollapse(); // Collapse entire tree
	treeWords->Items->EndUpdate();
}
//---------------------------------------------------------------------------

/*
	Simple function to add random nodes to a tree.
*/
void __fastcall TfrmMain::btnPopulateClick(TObject *Sender)
{
	treeWords->Items->BeginUpdate();

		if (treeWords->Items->Count == 0)
		{
			for (int i = 0; i < 3; i++)
			{
				TTreeNode *tn = treeWords->Items->Add(NULL, edtWordToAdd->Text);
				tn->ImageIndex = 0;
				tn->StateIndex = 5;
			}
		}

		int count = treeWords->Items->Count;
		for (int i = count - 1; i >= 0; i--)
		{
			TTreeNode *tn = treeWords->Items->Item[i];
			for (int j = 0; j <= Random(3) + 1; j++)
			{
				TTreeNode *node = treeWords->Items->AddChild(tn, edtWordToAdd->Text);
				node->ImageIndex = std::min(node->Level, lstImages->Count - 1);
				node->SelectedIndex = node->ImageIndex;
				node->StateIndex = 5;
			}
		}
		UpdateCount();
		btnExpandAllClick(Sender);

	treeWords->Items->EndUpdate();
}
//---------------------------------------------------------------------------

/*
	Empty the tree.
*/
void __fastcall TfrmMain::btnClearClick(TObject *Sender)
{
	ClearNodes();
}
//---------------------------------------------------------------------------

/*
	Handle the KeyUp event on the TreeView.
*/
void __fastcall TfrmMain::treeWordsKeyUp(TObject *Sender, WORD &Key, TShiftState Shift)
{
		// F2 key is pressed (edit)
	if (Key == VK_F2)
	{
			// Edit the selected node
		if (treeWords->Selected)
			treeWords->Selected->EditText();
	}
	else if (Key == VK_DELETE) // Delete key is pressed
	{
			// Delete the selected node
		if (treeWords->Selected)
				// If the node is being edited, *don't* delete it, otherwise do.
			if (!treeWords->IsEditing())
			{
				DeleteData(treeWords->Selected); // delete the node Data (recursively)
				treeWords->Selected->Delete();      // delete the nodes
				UpdateCount();
			}
	}
}
//---------------------------------------------------------------------------

/*
	Checks to see if the items can be dropped on the target.
*/
void __fastcall TfrmMain::treeWordsDragOver(TObject *Sender, TObject *Source,	int X, int Y, TDragState State, bool &Accept)
{
	Accept = false;
	TTreeView *tv = dynamic_cast<TTreeView *>(Source);
	if (!tv)
		return;

		// Only interested in dropping on our own tree
	if (Source != Sender)
		return;

	Accept = true;
}
//---------------------------------------------------------------------------

/*
	Handles nodes being dropped.
*/
void __fastcall TfrmMain::treeWordsDragDrop(TObject *Sender, TObject *Source,	int X, int Y)
{
	TTreeView *source_tv = (TTreeView*)Source;       // The "from" TreeView
	TTreeView *destination_tv = (TTreeView *)Sender; // The "To" TreeView

		// The node that is being dragged
	TTreeNode *sourceNode = source_tv->Selected;

		// The node that is being dropped onto
	TTreeNode *targetNode = destination_tv->GetNodeAt(X, Y);

		// Dropped onto nothing
	if (!targetNode)
		return;

		// Dropped onto the node that is being moved
	if (targetNode == sourceNode)
		return;

		// Dropping onto our parent is no good
	if (targetNode == destination_tv->Selected->Parent)
		return;

		// Can't drop "within" our own subtree
	if (IsAncestorNode(sourceNode, targetNode))
		return;

		// Do the copy
	MoveTreeNode(destination_tv, sourceNode, targetNode);

		// Show the nodes that were dropped
	targetNode->Expand(true);
}
//---------------------------------------------------------------------------

/*
	Simple function to populate the tree with a regular pattern. Invaluable for
	testing drag and drop behavior. 3 levels with 3 nodes at each level.
*/
void __fastcall TfrmMain::btnSimpleDemoClick(TObject *Sender)
{
	treeWords->Items->BeginUpdate();

		treeWords->Items->Clear();
		treeWords->Items->Add(NULL, "1")->ImageIndex = 0;
		treeWords->Items->Add(NULL, "2")->ImageIndex = 0;
		treeWords->Items->Add(NULL, "3")->ImageIndex = 0;

		int count = treeWords->Items->Count;
		for (int i = count - 1; i >= 0; i--)
		{
				// Add 3 subitems to each node
			TTreeNode *tn = treeWords->Items->Item[i];
			for (int j = 0; j < 3; j++)
			{
				AnsiString s = Format("%d.%d", ARRAYOFCONST((i + 1, j + 1)));
				TTreeNode *node = treeWords->Items->AddChild(tn, s);
				node->ImageIndex = std::min(node->Level, lstImages->Count - 1);
				node->SelectedIndex = node->ImageIndex;

					// Add 3 subitems to each node
				for (int k = 0; k < 3; k++)
				{
					AnsiString s = Format("%d.%d.%d", ARRAYOFCONST((i + 1, j + 1, k + 1)));
					TTreeNode *node2 = treeWords->Items->AddChild(node, s);
					node2->ImageIndex = std::min(node2->Level, lstImages->Count - 1);
					node2->SelectedIndex = node2->ImageIndex;
				}
			}
		}
		UpdateCount();
		btnExpandAllClick(Sender);

	treeWords->Items->EndUpdate();
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::treeWordsChange(TObject *Sender, TTreeNode *Node)
{
	UpdateNodeInfo(Node);
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::treeWordsMouseMove(TObject *Sender, TShiftState Shift, int X, int Y)
{
	TTreeNode *node = treeWords->GetNodeAt(X, Y);
	UpdateNodeInfo(node);
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::treeWordsMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
{
	TTreeNode *node = treeWords->GetNodeAt(X, Y);
	if (!node)
		return;

		// Expand the entire subtree when on a middle mouse click
		// If the Ctrl key is down, collapse the entire subtree
	if (Button == mbMiddle)
		if (Shift.Contains(ssCtrl))
		{
			treeWords->Items->BeginUpdate();
				node->Collapse(true); // recursively collapse
			treeWords->Items->EndUpdate();
		}
		else
		{
			treeWords->Items->BeginUpdate();
				node->Expand(true);   // recursively expand
			treeWords->Items->EndUpdate();
		}
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::treeWordsMouseDown(TObject *Sender,	TMouseButton Button, TShiftState Shift, int X, int Y)
{
	TTreeNode *node = treeWords->GetNodeAt(X, Y);
	if (!node)
		return;

		// If the user clicks the left mouse button with the
		// Control key down, toggle the state image
	if (Button == mbLeft)
		if (Shift.Contains(ssCtrl))
			node->StateIndex = (node->StateIndex == 5) ? 6 : 5;
}
//---------------------------------------------------------------------------

/*
	When files are dropped onto the form, this handles the event.
*/
void __fastcall TfrmMain::WMDropFiles(TWMDropFiles &Message)
{
	int num_files, filename_length;
	char file_name[MAX_PATH + 1];

		// Refer to the Win SDK documentation about the details
		// of DragQueryFile and the HDROP structure

		// Get the number of files that are being dropped on the app
	num_files = DragQueryFile((HDROP)Message.Drop, -1, NULL, 0);
	if (num_files < 1)
		return;

	treeWords->Items->BeginUpdate();
		std::clock_t start = std::clock();
		lblTime->Caption = "Working...";

			// Retrieve each filename, one at a time
		for(int i = 0; i < num_files; i++)
		{
				// Get the length of the i'th filename
			filename_length = DragQueryFile((HDROP)Message.Drop, i, NULL, 0);

				// Retrieve the i'th filename from the HDROP structure
			DragQueryFile((HDROP)Message.Drop, i, file_name, filename_length + 1);

				// Add the file/folder to the list (and children, if folder)
			AddFiles(file_name);
		}
		std::clock_t end = std::clock();
		lblTime->Caption = Format("%d ms", ARRAYOFCONST((end - start)));
	treeWords->Items->EndUpdate();
}

void __fastcall TfrmMain::btnStopClick(TObject *Sender)
{
	AbortProcess = true;
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::btnSortClick(TObject *Sender)
{
	treeWords->AlphaSort(true);
}
//---------------------------------------------------------------------------

/*
	When an item in the tree is double-clicked, open it with the appropriate
	application. See MSDN for more information on ShellExecute and associated
	functions, e.g. ShellExecuteEx, and the ShellAPI in shellapi.h.
*/
void __fastcall TfrmMain::treeWordsDblClick(TObject *Sender)
{
		// If something is double-clicked, it will be the only one selected
	TTreeNode *selnode = treeWords->Selected;
	if (!selnode)
		return; // should never happen

		// The Data stores the entire path (the text is only the filename portion)
		// We've stored it as a NULL-terminated string there.
	char *str = (char *)selnode->Data;
	if (str)
		ShellExecute(Handle, "open", str, NULL, NULL, SW_SHOWNORMAL);
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::Timer1Timer(TObject *Sender)
{
	UpdateCount();
}
//---------------------------------------------------------------------------

//******************************************************************************
//******************************************************************************
// Support functions
//******************************************************************************
//******************************************************************************

/*
	Given a filename, add it to the top level of the tree. If Filename is a
	directory, 	recursively add its descendants to the tree at the proper
	location.
*/
void TfrmMain::AddFiles(const AnsiString &Filename)
{
		// Add at the top level (only the filename portion)
	TTreeNode *tn = treeWords->Items->Add(NULL, ExtractFileName(Filename));

		// Store the entire name (i.e. including the absolute path) in the Data
	AddData(tn, Filename);

		// If it's not a directory (it's a file), just add the filename
	if (!DirectoryExists(Filename))
	{
		tn->ImageIndex = 4; // file icon (magic number!)
	}
	else // otherwise it's a directory, recurse
	{
		tn->ImageIndex = 1; // folder icon (magic number!)

			// Add any children to this node (files/folders)
		AddSubFiles(tn, Filename);
	}
	UpdateCount(); // Update UI counter
}

/*
	Adds all files/folders to parent
*/
void TfrmMain::AddSubFiles(TTreeNode *parent, const AnsiString &Directory)
{
	TStringList *filespecs = new TStringList();
	filespecs->Add("*.*"); // Currently, we want all files
	AbortProcess = false;

		// Recursively  finds and adds all files/folders to the tree
	FindFiles(Directory, *filespecs, true, treeWords, parent);
	delete filespecs;
}

/*
	Finds all files in the directory "Directory" of the type "FileSpec" and
	adds them to Tree at parent. Example:

		CatalogFiles("C:\Windows\System32", "*.exe", SomeTreeView, SomeNode);

*/
int TfrmMain::CatalogFiles(const AnsiString& Directory, const AnsiString& FileSpec,
													 TTreeView* Tree, TTreeNode *parent)
{
	TSearchRec finfo;
	int done, count = 0;
	AnsiString dir;

		//Make sure there is a trailing "/"
	dir = IncludeTrailingPathDelimiter(Directory);

		// Prime the search (See help on FindFirst/FindNext)
	done = FindFirst(dir + FileSpec, faAnyFile, finfo);

		// Until no more files match (or the user stopped it)
	while ((done == 0) && (!AbortProcess))
	{
			// Ignore current directory and parent directory
		if ((finfo.Name != ".") && (finfo.Name != ".."))
		{
			//AnsiString name = Format("[%p] %s", ARRAYOFCONST((parent, finfo.Name)));
			AnsiString name = finfo.Name;

				// Add the filename to the list
			TTreeNode *tn = Tree->Items->AddChild(parent, name /* finfo.Name*/);

				// Store the entire name (i.e. including the absolute path) in the Data
			AddData(tn, dir + name);

			tn->ImageIndex = 4; // default to file icon (magic number!)
			count++;
		}
			// Another match?
		done = FindNext(finfo);

			// Yield to the application
		Application->ProcessMessages();
	}
		// Done searching
	FindClose(finfo);
	return count;
}

/*
	Finds all files in the directory "Directory" of the type "FileSpec" and
	adds them to "List". If "IncludeSubdirectories" is true, it will call
	itself recursively. Example:

		FindFiles("C:\Windows\System32", "*.exe", true, SomeTreeView, SomeNode);

	The real work is done by the CatalogFiles above.

*/
int TfrmMain::FindFiles(const AnsiString& Directory, TStringList& FileSpecs, bool IncludeSubdirectories,
												TTreeView *Tree, TTreeNode *parent)
{
	TSearchRec finfo;
	int done, count = 0;
	AnsiString filespec, dir, dirspec;

		//Make sure there is a trailing "/"
	dir = IncludeTrailingPathDelimiter(Directory);

		// Always search for all when looking for directories
	dirspec = dir + "*.*";

		// Get all files for each spec
	for (int i = 0; i < FileSpecs.Count; i++)
	{
			// Get the next filespec
		filespec = FileSpecs[i];
			// Find matching files
		count += CatalogFiles(dir, filespec, Tree, parent);
	}

		// Traverse directories?
	if (IncludeSubdirectories)
	{
			// Find the first file that matches
		done = FindFirst(dirspec, faAnyFile, finfo);

			// While there are more files that match (and the user doesn't abort)
		while ((done == 0) && (!AbortProcess))
		{
				// If it's a directory (and not current nor parent), traverse it
			if (((finfo.Attr & faDirectory) != 0) && (finfo.Name != ".") && (finfo.Name != ".."))
			{
				AnsiString subdir = Format("%s%s", ARRAYOFCONST((dir, finfo.Name)));

					// Find this node in the tree and use it as the parent during
					// the recursion
				TTreeNode *newparent = FindChild(parent, finfo.Name);
				if (newparent)
				{
					newparent->ImageIndex = 1; // folder icon (magic number!)
					count += FindFiles(subdir, FileSpecs, IncludeSubdirectories, Tree, newparent);
				}
			}

				// Yield to update the UI
			Application->ProcessMessages();

				// Another match?
			done = FindNext(finfo);
		}
		FindClose(finfo);
	}
	return count;
}

/*
	Given a TTreeNode and text, find the child containing the text.
*/
TTreeNode * TfrmMain::FindChild(TTreeNode *parent, const AnsiString &str)
{
		// Iterate through the children looking for str
	TTreeNode *tn = parent->getFirstChild();
	while (tn)
	{
		if (tn->Text == str) // found
			return tn;

		tn = parent->GetNextChild(tn);
	}
	return 0; // not found
}

/*
	Allocates space for the string and assigns it to the tree node.
*/
void TfrmMain::AddData(TTreeNode *node, const AnsiString &path)
{
	if (node)
	{
		int size = path.Length();
		node->Data = new char[size + 1];
		strcpy((char *)node->Data, (char *)path.c_str());
	}
}

/*
	Recursively delete the Data portion of the node
*/
void TfrmMain::DeleteData(TTreeNode *node)
{
	if (!node)
		return;

	delete [] node->Data;

		// How many children for this node? TTreeNode::Count is O(N)!
	int num_children = node->Count;
	for (int i = 0; i < num_children; i++)
		DeleteData(node->Item[i]);
}

/*
	Iterates through the tree deleting all of the Data and all of the nodes.
*/
void TfrmMain::ClearNodes(void)
{
	int count = treeWords->Items->Count;

		// Delete the Data stroed in the nodes
	for (int i = 0; i < count; i++)
	{
		//if (!treeWords->Items->Item[i]->Parent)
		if (treeWords->Items->Item[i]->Level == 0)
			DeleteData(treeWords->Items->Item[i]);
	}
	treeWords->Items->Clear(); // Delete the nodes
	UpdateCount();
	UpdateNodeInfo(0);
}

