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

#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);
}