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

#include <vcl.h>
#pragma hdrstop

#include "MainForm.h"
#include <ShlObj.hpp>
#include <ClipBrd.hpp>
#include <System.hpp>

#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ActnList.hpp>
#include <ComCtrls.hpp>
#include <ExtCtrls.hpp>
#include <Menus.hpp>
#include <ToolWin.hpp>

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

typedef struct _DROPFILES {
   DWORD pFiles;                       // offset of file list
   POINT pt;                           // drop point (client coords)
   BOOL fNC;                           // is it on NonClient area
                                       // and pt is in screen coords
   BOOL fWide;                         // WIDE character switch
} DROPFILES, *LPDROPFILES;

//---------------------------------------------------------------------------
__fastcall TfrmMain::TfrmMain(TComponent* Owner) : TForm(Owner)
{
  FReverseSortOrder[0] = false;
  FReverseSortOrder[1] = false;
  FReverseSortOrder[2] = false;
  FReverseSortOrder[3] = false;
  FReverseSortOrder[4] = false;

  FThreadCount[0] = 0;
  FThreadCount[1] = 0;
  FThreadCount[2] = 0;

  FStatePanels[0] = pnlState1;
  FStatePanels[1] = pnlState2;
  FStatePanels[2] = pnlState3;

  FCountLabels[0] = lblCount1;
  FCountLabels[1] = lblCount2;
  FCountLabels[2] = lblCount3;

  FPriorityList[0] = lstPriority1;
  FPriorityList[1] = lstPriority2;
  FPriorityList[2] = lstPriority3;

  FFileList[0] = new TStringList();
  FFileList[1] = new TStringList();
  FFileList[2] = new TStringList();

}
//---------------------------------------------------------------------------

__fastcall TfrmMain::~TfrmMain(void)
{
  delete FFileList[0];
  delete FFileList[1];
  delete FFileList[2];
}

/*
  Called by a thread that has completed (or been terminated)
*/
void __fastcall TfrmMain::ThreadCompleted(long ID)
{
    // Sanity check
  if (ID >= 0 && ID <= 2)
  {
      // Update the status
    FStatePanels[ID]->Color = clRed;
    FStatePanels[ID]->Caption = "Done";
    FCountLabels[ID]->Caption = IntToStr(FFileList[ID]->Count);
    lblCount->Caption = IntToStr(FFileCount);
    Application->ProcessMessages();

      // If the threads didn't call back for each file, this is the
      // only place that the list of files will be available.
    if (!chkCallbackForEachFile->Checked)
    {
      FFileCount += FFileList[ID]->Count;
      lblCount->Caption = IntToStr(FFileCount);
      FCountLabels[ID]->Caption = IntToStr(FFileList[ID]->Count);
      Application->ProcessMessages();

        // Display output?
      if (chkShowOutput->Checked)
      {
          // For each file in the list
        for (int i = 0; i < FFileList[ID]->Count; i++)
        {
            // Add a new item and set the caption only
          TListItem *item = lstFiles->Items->Add();
          item->Caption = FFileList[ID]->Strings[i];
          //TListItem *item = lstFiles->Items->Insert(0);
        }
      }
    }
    FThreads[ID] = 0; // thread is invalid now
  }
}

/*
  Called by each thread as a file is found during the search.
*/
void __fastcall TfrmMain::ProcessFile(const TFileFindInfo& FInfo, long ID)
{
    // Update couners
  FFileCount++;
  FThreadCount[ID]++;

    // Update UI
  FCountLabels[ID]->Caption = IntToStr(FThreadCount[ID]);
  lblCount->Caption = IntToStr(FFileCount);
  if (chkShowOutput->Checked)
    AddFile(FInfo, ID);
  Application->ProcessMessages();
}

/*
  Adds a new item to the list view
*/
void TfrmMain::AddFile(const TFileFindInfo& FInfo, long ID)
{
    // 'Add' creates a new, empty item in the list view
  TListItem *item = lstFiles->Items->Add();
  //TListItem *item = lstFiles->Items->Insert(0);

    // Set each "field" in the list view (in this order)
  item->Caption = FInfo.Name;
  item->SubItems->Add(FInfo.Path);

    // Type
  if (FInfo.IsFolder)
    item->SubItems->Add(""); // don't display size for folders
  else
    item->SubItems->Add(FInfo.Size);

  item->SubItems->Add(FInfo.Extension);
  item->SubItems->Add(FInfo.DateModified);

    // Files and folders have different icons
  if (FInfo.IsFolder)
    item->ImageIndex = 1;
  else
    item->ImageIndex = 0;
}

/*
  For comparing strings
*/
int __fastcall TfrmMain::CompareTextFields(const AnsiString& Item1, const AnsiString& Item2)
{
  return CompareText(Item1, Item2);
}

/*
  For comparing integers
*/
int __fastcall TfrmMain::CompareIntegerFields(int Item1, int Item2)
{
  return Item1 < Item2;
}

/*
  For comparing dates
*/
int __fastcall TfrmMain::CompareDateFields(const TDateTime& Item1, const TDateTime& Item2)
{
  return Item1 < Item2;
}

/*
  These next 5 functions are callbacks for the sort function of the list view.
  Each column is hooked up to the appropriate sort function.
*/
void __fastcall TfrmMain::CompareNameField(TObject *Sender, TListItem* Item1, TListItem* Item2, int Data, int& Compare)
{
  if (FReverseSortOrder[0])
    Compare = CompareTextFields(Item2->Caption, Item1->Caption);
  else
    Compare = CompareTextFields(Item1->Caption, Item2->Caption);
}

void __fastcall TfrmMain::ComparePathField(TObject *Sender, TListItem* Item1, TListItem* Item2, int Data, int& Compare)
{
  if (FReverseSortOrder[1])
    Compare = CompareTextFields(Item2->SubItems->Strings[0], Item1->SubItems->Strings[0]);
  else
    Compare = CompareTextFields(Item1->SubItems->Strings[0], Item2->SubItems->Strings[0]);
}

void __fastcall TfrmMain::CompareSizeField(TObject *Sender, TListItem* Item1, TListItem* Item2, int Data, int& Compare)
{
  int size1 = 0, size2 = 0;
  if (Item1->SubItems->Strings[1] != "")
    size1 = StrToInt(Item1->SubItems->Strings[1]);
  if (Item2->SubItems->Strings[1] != "")
    size2 = StrToInt(Item2->SubItems->Strings[1]);

  if (FReverseSortOrder[2])
    Compare = CompareIntegerFields(size1, size2);
  else
    Compare = CompareIntegerFields(size2, size1);
}

void __fastcall TfrmMain::CompareTypeField(TObject *Sender, TListItem* Item1, TListItem* Item2, int Data, int& Compare)
{
  if (FReverseSortOrder[3])
    Compare = CompareTextFields(Item2->SubItems->Strings[2], Item1->SubItems->Strings[2]);
  else
    Compare = CompareTextFields(Item1->SubItems->Strings[2], Item2->SubItems->Strings[2]);
}

void __fastcall TfrmMain::CompareDateField(TObject *Sender, TListItem* Item1, TListItem* Item2, int Data, int& Compare)
{
  if (FReverseSortOrder[4])
    Compare = CompareDateFields(StrToDateTime(Item1->SubItems->Strings[3]), StrToDateTime(Item2->SubItems->Strings[3]));
  else
    Compare = CompareDateFields(StrToDateTime(Item2->SubItems->Strings[3]), StrToDateTime(Item1->SubItems->Strings[3]));
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// Event Handlers
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

void __fastcall TfrmMain::FormCreate(TObject *Sender)
{
  lstFiles->Align = alClient;
  Width = 725;
  Height = 750;
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::FormClose(TObject *Sender, TCloseAction &Action)
{
    // Stop the threads before closing the form
  btnStopAllClick(Sender);
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::btnStartClick(TObject *Sender)
{
  FThreadCount[0] = 0;
  FThreadCount[1] = 0;
  FThreadCount[2] = 0;
  FFileCount = 0;
  lblCount1->Caption = "0";
  lblCount2->Caption = "0";
  lblCount3->Caption = "0";
  lblCount->Caption = "0";

  AnsiString paths[] = {lstPath1->Text, lstPath2->Text, lstPath3->Text};

  FFileList[0]->Clear();
  FFileList[1]->Clear();
  FFileList[2]->Clear();

  TStringList *filespecs = new TStringList();
  filespecs->Add(lstFileSpecs->Text);
  lstFiles->Clear();

    // Create 3 thread objects and set their attributes
  for (int i = 0; i < 3; i++)
  {
    FThreads[i] = new TFileFindThread();
    FThreads[i]->ID = i;
    FThreads[i]->Directory = paths[i];
    FThreads[i]->FileSpecs = filespecs;
    FThreads[i]->IncludeSubdirectories = chkRecurseSubdirectories->Checked;
    FThreads[i]->FileList = FFileList[i];
    FThreads[i]->Priority = TThreadPriority(FPriorityList[i]->ItemIndex);

    if (chkCallbackForEachFile->Checked)
      FThreads[i]->CallbackProc = &ProcessFile;

    FThreads[i]->CompletedProc = &ThreadCompleted;
  }

    // Start the threads
  for (int i = 0; i < 3; i++)
  {
    FThreads[i]->Resume();
    FStatePanels[i]->Color = clLime;  // I like it better than clGeen
    FStatePanels[i]->Caption = "Running";
  }
  delete filespecs;
}
//---------------------------------------------------------------------------

/*
  Column was clicked in the list view, so sort based on the column
*/
void __fastcall TfrmMain::lstFilesColumnClick(TObject *Sender, TListColumn *Column)
{
    // Set comparison callback based on the column that was clicked
  if (Column->Index == 0)
    lstFiles->OnCompare = CompareNameField;
  else if (Column->Index == 1)
    lstFiles->OnCompare = ComparePathField;
  else if (Column->Index == 2)
    lstFiles->OnCompare = CompareSizeField;
  else if (Column->Index == 3)
    lstFiles->OnCompare = CompareTypeField;
  else if (Column->Index == 4)
    lstFiles->OnCompare = CompareDateField;
  else
    return;

    // enum TSortType { stNone, stData, stText, stBoth };
  lstFiles->SortType = Comctrls::stData; // Set sort type
  lstFiles->AlphaSort();                 // Perform the sort
  lstFiles->SortType = Comctrls::stNone; // Reset sort to None

    // Toggle sort order (ascending/descending) after each sort
  FReverseSortOrder[Column->Index] = !FReverseSortOrder[Column->Index];
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::btnResumeClick(TObject *Sender)
{
  TButton *button = (TButton *)Sender;
  if (FThreads[button->Tag])
  {
    FThreads[button->Tag]->Resume();
    FStatePanels[button->Tag]->Color = clLime;
    FStatePanels[button->Tag]->Caption = "Running";
  }
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::btnSuspendClick(TObject *Sender)
{
  TButton *button = (TButton *)Sender;
  if (FThreads[button->Tag])
  {
    FThreads[button->Tag]->Suspend();
    FStatePanels[button->Tag]->Color = clYellow;
    FStatePanels[button->Tag]->Caption = "Suspended";
  }
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::btnStopClick(TObject *Sender)
{
  TButton *button = (TButton *)Sender;
  if (FThreads[button->Tag])
  {
    FThreads[button->Tag]->Terminate(); // sets terminated flag
    FThreads[button->Tag]->Resume();    // need to let the thread clean up
  }
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::btnSuspendAllClick(TObject *Sender)
{
  btnSuspendClick(btnSuspend1);
  btnSuspendClick(btnSuspend2);
  btnSuspendClick(btnSuspend3);
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::btnStopAllClick(TObject *Sender)
{
  for (int i = 0; i < 3; i++)
  {
    if (FThreads[i])
    {
      FThreads[i]->Terminate(); // set Terminated flag
      FThreads[i]->Resume();    // need to let the thread clean up
    }
  }
}
//---------------------------------------------------------------------------

/*
  When a file is double-clicked in the list view, "launch" it.
*/
void __fastcall TfrmMain::lstFilesDblClick(TObject *Sender)
{
    // Get selected item (the one that was clicked on)
  TListItem *item = lstFiles->Selected;
  if (!item)
    return;

    // There is different data in the list view depending on the state of
    // the callback
  AnsiString s;
  if (chkCallbackForEachFile->Checked)
    s = Format("%s", ARRAYOFCONST((item->SubItems->Strings[0] + item->Caption)));
  else
    s = Format("%s", ARRAYOFCONST((item->Caption)));

    // See the online help for the ShellExecute API function and verbs
  AnsiString verb = "open";
  if (DirectoryExists(s))
  {
    if (grpFolderShellExecuteVerb->ItemIndex == 1)
      verb = "explore";
    else if (grpFolderShellExecuteVerb->ItemIndex == 2)
      verb = "find";
  }
  else
  {
    if (grpFileShellExecuteVerb->ItemIndex == 1)
      verb = "edit";
  }
  ShellExecute(NULL, verb.c_str(), s.c_str(), NULL, NULL, SW_SHOW);
}
//---------------------------------------------------------------------------

/*
  Event handler for when a key is down.
*/
void __fastcall TfrmMain::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift)
{
    // If the keys Control and C are down, copy the selected files to the
    // clipboard
  if ( (Key == 'C' || Key == 'c') && Shift.Contains(ssCtrl))
      CopySelectedToClipboard();
}
//---------------------------------------------------------------------------

/*
  Event handler for when an item is selected. Makes it easy to display the
  number of items that are currently selected. (Displayed in the status bar.)
*/
void __fastcall TfrmMain::lstFilesSelectItem(TObject *Sender, TListItem *Item, bool Selected)
{
  barStatus->SimpleText = " Files selected: " + IntToStr(lstFiles->SelCount);
}
//---------------------------------------------------------------------------

/*
  Iterates through the items in the list view. If the item is
  highlighted, add it to a list. This list is then passed to
  a function that will copy the filenames to the clipboard so
  they can be pasted in Explorer.
*/
void TfrmMain::CopySelectedToClipboard(void)
{
    // Get the selected item
  TListItem* item = lstFiles->Selected;
  if (!item)
    return;

    // Filenames will be put into this list
  TStringList *files = new TStringList();

    // Loop through all of the highlighted items
  while (item != NULL)
  {
      // Construct full path name
    AnsiString file = item->SubItems->Strings[0] + item->Caption;
    files->Add(file);

      // From the help:
      // TListItem *GetNextItem(StartItem, Direction, States)
      // Call GetNextItem to find the next list item after StartItem in the
      // direction given by the Direction parameter. Only items in the state
      // indicated by the States parameter are considered.
    item = lstFiles->GetNextItem(item, sdAll, TItemStates() << isSelected) ;
  }
    // This function formats the filenames for the clipboard copy/paste
  PutFilesOnClipboard(files);
  delete files;
}


/*
  Take a list of filenames and format them so that they can be placed
  on the clipboard. The format is simply a long string of characters.
  Each filename in the "string" is separated by a NULL character. The
  last filename has two NULL characters. As you can see, this is not
  a typical C-style NULL-terminated string, so you must be careful
  not to treat it as such.
*/
void TfrmMain::PutFilesOnClipboard(TStringList* Filelist)
{
    // No files in the list
  if (!Filelist->Count)
    return;

  TDropFiles *DropFiles;
  HGLOBAL globalMem;
  AnsiString files;
  int len = 0;

    // Calculate how much space is needed to hold all of the filenames
    // and the extra NULL characters.
  for (int i = 0; i < Filelist->Count; i++)
  {
    len += Filelist->Strings[i].Length();
    len++; // additional NULL to be included
  }
  len++; // include the second NULL terminator at the end

    // Create the buffer to hold all of the characters
  char *buffer = new char[len];
  std::memset(buffer, 0, len);
  char *next = buffer;

    // Copy each filename into the buffer and add an extra NULL character
  for (int i = 0; i < Filelist->Count; i++)
  {
      // Copy the filename starting at 'next' in the buffer
    strcpy(next, Filelist->Strings[i].t_str());

      // Point at the NULL immediately following this string
    next += Filelist->Strings[i].Length();

      // Point one further (we need to keep the NULL between filenames)
    next++;
  }

    // Use GlobalAlloc when dealing with the clipboard
  globalMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, sizeof(TDropFiles) + len);
  if (!globalMem)
  {
    ShowMessage("Can't alloc memory.");
    delete [] buffer;
    return;
  }

    // Lock the memory (it's moveable)
  DropFiles = (TDropFiles *)GlobalLock(globalMem);
  if (!DropFiles)
  {
    ShowMessage("Can't lock memory.");
    delete [] buffer;
    return;
  }

    // The size of the struct
  DropFiles->pFiles = sizeof(TDropFiles);

  void *source = (void*)buffer;
  void *dest = (char *)(DropFiles) + sizeof(TDropFiles);

    // Copy the data from the buffer to the allocated memory
    // Move handles overlaps between the source and destination
  Move(source, dest, len);

    // Unlock the memory
  GlobalUnlock(globalMem);

    // Assign the memory to the clipboard (it will free it)
    // and tell the clipboard it has drag/drop data
  Clipboard()->SetAsHandle(CF_HDROP, (THandle)globalMem);

  delete [] buffer;
}