// TODO Этот модуль по факту используется тольк в mus, нужно переименовать
// его в dfileiterator_ex, а dfileiterator_nosysutils в dfileiterator
{$MODESWITCH AUTODEREF}
unit dfileiterator;

interface

uses
  SysUtils,
  dtypes;

const
  FI_FILES              = faAnyFile;
  FI_DIRS               = faDirectory;
  FI_SYSTEM             = FI_FILES or FI_DIRS;

  FI_ADDITIONAL         = not FI_SYSTEM;
  FI_ADDITIONAL_FIRST   = FI_ADDITIONAL and not (FI_ADDITIONAL - 1);
  FI_RECURSIVE          = FI_ADDITIONAL_FIRST;
  FI_DEFAULT            = FI_FILES or FI_RECURSIVE;

type
TFileIterator = object
private
  FReady: Boolean;
  FFlags: Integer;
  FSearch: ^TSearchRec;
  FRoot: TString;
  // TODO переделать на core/collections/dstack
  FStack: array of TSearchRec;
  function GetName: TString;
  function GetSize: Integer;
  function GetTime: Integer;
  function GetFullName: TString;
  function GetRelName: TString;
public
  constructor Init(const Path: TString; Flags: Integer = FI_DEFAULT);
  destructor Done;
  function Next: Boolean;

  function IsFile: Boolean; inline;
  function IsDirectory: Boolean; inline;
  property Name: TString read GetName;
  property FullName: TString read GetFullName;
  property RelName: TString read GetRelName;
  property Size: Integer read GetSize;
  property Time: Integer read GetTime;
end;

type
  TString = AnsiString;
  TStrings = array of TString;
function Ls(const Path: TString): TStrings;

implementation

constructor TFileIterator.Init(const Path: TString; Flags: Integer = FI_DEFAULT);
var
  Attr: LongInt;
begin
  FFlags := Flags;
  SetLength(FStack, 1);
  FSearch := @FStack[0];
  FRoot := Path;
  FReady := FindFirst(Path + DirectorySeparator + '*', FFlags and FI_SYSTEM, FSearch^) = 0;
end;

destructor TFileIterator.Done;
begin
  FindClose(FSearch^);
end;

function TFileIterator.Next: Boolean;
var
  Path: TString;
begin
    if ((FFlags and FI_RECURSIVE) = FI_RECURSIVE) and IsDirectory and (Name <> '.') and (Name <> '..') then begin
      Path := GetFullName;
      SetLength(FStack, Length(FStack) + 1);
      Result := FindFirst(Path + DirectorySeparator + '*', FFlags and FI_SYSTEM, FStack[High(FStack)]) = 0;
      FSearch := @FStack[High(FStack)];
    end else begin
      Result := FindNext(FSearch^) = 0;
      while Result and ((Name = '.') or (Name = '..')) do
        Result := FindNext(FSearch^) = 0;
    end;
    if (FFlags and FI_RECURSIVE) = FI_RECURSIVE then
      while not Result do begin
        if Length(FStack) = 1 then
          Exit;
        FindClose(FSearch^);
        SetLength(FStack, Length(FStack) - 1);
        FSearch := @FStack[High(FStack)];
        Result := FindNext(FSearch^) = 0;
        while Result and ((Name = '.') or (Name = '..')) do
          Result := FindNext(FSearch^) = 0;
      end;
end;

function TFileIterator.GetName: TString;
begin
  Result := FSearch^.Name;
end;

function TFileIterator.GetSize: Integer;
begin
  Result := FSearch^.Size;
end;

function TFileIterator.GetTime: Integer;
begin
  Result := FSearch^.Time;
end;

function TFileIterator.GetFullName: TString;
begin
  Result := FRoot + DirectorySeparator + GetRelName;
end;

function TFileIterator.GetRelName: TString;
var
  I: Integer;
begin
  Result := '';
  for I := 0 to High(FStack) do begin
    if I > 0 then
      Result := Result + DirectorySeparator;
    Result := Result + FStack[I].Name;
  end;
end;

function TFileIterator.IsFile: Boolean;
begin
  Result := not IsDirectory
end;

function TFileIterator.IsDirectory: Boolean;
begin
  Result := (FSearch^.Attr and faDirectory) = faDirectory;
end;

function Ls(const Path: TString): TStrings;
var
  It: TFileIterator;
begin
  SetLength(Result, 0);
  It.Init(Path, FI_FILES or FI_DIRS);
  while It.Next do begin
    SetLength(Result, Length(Result) + 1);
    Result[High(Result)] := It.Name;
  end;
  It.Done;
end;

end.
