########################################################################
##
## Copyright (C) 2004-2022 The Octave Project Developers
##
## See the file COPYRIGHT.md in the top-level directory of this
## distribution or <https://octave.org/copyright/>.
##
## This file is part of Octave.
##
## Octave is free software: you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## Octave is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Octave; see the file COPYING.  If not, see
## <https://www.gnu.org/licenses/>.
##
########################################################################

## -*- texinfo -*-
## @deftypefn  {} {} dir
## @deftypefnx {} {} dir (@var{directory})
## @deftypefnx {} {[@var{list}] =} dir (@var{directory})
## Display file listing for directory @var{directory}.
##
## If @var{directory} is not specified then list the present working directory.
##
## If a return value is requested, return a structure array with the fields
##
## @table @asis
## @item name
## File or directory name.
##
## @item folder
## Location of file or directory
##
## @item date
## Timestamp of file modification (string value).
##
## @item bytes
## File size in bytes.
##
## @item isdir
## True if name is a directory.
##
## @item datenum
## Timestamp of file modification as serial date number (double).
##
## @item statinfo
## Information structure returned from @code{stat}.
## @end table
##
## If @var{directory} is a filename, rather than a directory, then return
## information about the named file.  @var{directory} may also be a list rather
## than a single directory or file.
##
## @var{directory} is subject to shell expansion if it contains any wildcard
## characters @samp{*}, @samp{?}, @samp{[]}.  If these wildcard characters are
## escaped with a backslash @samp{\} (e.g., @samp{\*}) on a POSIX platform,
## then they are not treated as wildcards, but as the corresponding literal
## character.  On Windows, it is not possible to escape wildcard characters
## because backslash @samp{\} is treated as a file separator.  On Windows, use
## @code{ls} for file or folder names that contain characters that would be
## treated as wildcards by @code{dir}.
##
## Note that for symbolic links, @code{dir} returns information about the
## file that the symbolic link points to rather than the link itself.  However,
## if the link points to a nonexistent file, @code{dir} returns information
## about the link.
## @seealso{ls, readdir, glob, what, stat, lstat}
## @end deftypefn

function u = dir (d = '', w = '', recursive = 0)

   if (!ischar (d))
      print_usage ();
   endif

   f = filesep;                         ## file seperator

   if (recursive == 0)

      ## change "wrong" slashes to fit the actual system plattform
      if (f == '\')
         d(d == '/') = f;  ## windows
      else
         d(d == '\') = f;  ## linux
      endif

      ## check if recursive listing is desired and remove recursive tag from path
      recursive =! isempty (strfind (d, '**'));
      recursiveProcessingActive = recursive; ## remember recursive processing
      if (recursive)
         d = strrep (d, '**', '');   ## remove recursive tag in d
      endif

      ## if d is empty or has spaces at the beginning
      if isempty (d)
         d = pwd;
      end
      d=strtrim(d);

      ## sometimes d is given with double slashes
      d = [d(1),strrep(d(2:end),[f,f],f)]; ## //->/, preserve // at the beginning for net drives
      d = d(1:end - (d(end) == f));        ## cut last char if its a slash

      ## seperate d and w
      if ( sum (d == f | d==':') > 0)      ## check if d is a complex path with slashes
         i = max (find (d == f));          ## last slash in string
         w = d( (i + 1):end);              ## ending could be wildcard
      else
         w = d;                            ## if it is without slash than take all
         d = [pwd, f, w];                  ## construct d
      endif

      if (isempty (strfind (w, '*')))      ## check if the ending is a:
         w = '*';                          ## 1. complete directory
      else
         d = d(1:end - length (w) -1 );    ## 2. or wildcard
      endif

      if isempty (d) d = pwd; end

   endif

   ## read actual directory files
   u = __wglob__ ([d, f, w]);

   ## add '.' and '..* directory entries if all files are searched
   if (w=='*')
      u = [[d,f,'.'];[d,f,'..'];u];
   endif

   ## recurse one level deeper if there are directories present
   if (recursive > 0)
      v = __wglob__ ([d, f, '*', f]);      ## list ONLY directories
      for i = 1:numel (v)
         u = [u;dir(v{i}(1:end - 1),w,recursive + 1)];  ## cut slash at the end of v{i}
      endfor
      recursive = recursive - 1;
   endif

   ## return variable or print result if the recursion level is 0
   if ( (recursive == 0) && (!isempty (u)) )

      if (nargout > 0)

         ## return the directory as a variable

         ## mtime is unix time = seconds since 1.1.1970
         ## datenum is days since 1.1.0000
         ## there is just a constant time difference, only a + is needed
         dt = datenum (1970, 1, 1, 1, 0, 0); ## time difference
         n = numel (u);
         st = cellfun (@(x) stat (x), u);   ## in former dir.m lstat was used => check with linux
         b = struct (...
         'name', cellfun (@(x) x(max (find (x == f)) + 1:end), u, 'UniformOutput', false),...
         'folder', cellfun (@(x) x(1:max (find (x == f)) - 1), u, 'UniformOutput', false),...
         'date', mat2cell (datestr ([st.mtime]'./(24 .* 3600) + dt), ones (n, 1)),...
         'bytes', {st.size}',...
         'isdir', cellfun (@(x) S_ISDIR (x), {st.mode}', 'UniformOutput', false),...
         'datenum', num2cell ([st.mtime]'./(24 .* 3600) + dt),...
         'statinfo', mat2cell (st, ones (n, 1))...
         );
         u = b;

      else

         ## print the output

         if (recursiveProcessingActive)
            ## ... in case of recursive processing at the end of the processing
            v=cellfun (@(x) x(1:max (find (x == f)) - 1), u, 'UniformOutput', false); ## get folder list
            [v, k] = unique (v);        ## eliminate duplicates
            v(:, 2) = num2cell (k);     ## sort in correct order, like it occured
            v = sortrows (v, 2);        ## ...
            k = cell2mat (v(:, 2));     ## read the indices of the directory jumps into k
            k(end + 1) = numel (u) + 1; ## add length of u+1
            for i = 1:numel (k) - 1      ## print the directories after each other
               printf ([newline,'Files Found in: %s',newline,newline], v{i, 1}); ## directory header
               printf ("%s", list_in_columns ( cellfun (@(x) x(max (find (x == f)) + 1:end), u( k(i):(k(i + 1) - 1) ), 'UniformOutput', false) ) );
            endfor
         else
            ## ... in case if linear processing... print just the file list without a header
            printf ("%s", list_in_columns ( cellfun (@(x) x(max (find (x == f)) + 1:end), u, 'UniformOutput', false) ) );
         endif

         clear u  ## no u can be given back :) => nargout==0

      endif

   endif

endfunction

