// ls - Windows NT directory listing utility
//
// Author: Jon S. Whalen (c) 1993
//   internet: jon@hook.corp.mot.com
//   compuserve: 76665,3043
//
// 6 March 1993
// Release 1.0 'ls.exe'

#include <windef.h>
#include <excpt.h>
#include <winbase.h>
#include <iostream.h>
#include <stdlib.h>
#include <winuser.h>
//#include <wincon.h>

// the structs and functions are declared in
// wincon.h, but not for C++, so they're included here to get
// the linkage to work.

typedef struct _COORD {
    SHORT X;
    SHORT Y;
} COORD, *PCOORD;

typedef struct _SMALL_RECT {
    SHORT Left;
    SHORT Top;
    SHORT Right;
    SHORT Bottom;
} SMALL_RECT, *PSMALL_RECT;

typedef struct _CONSOLE_SCREEN_BUFFER_INFO {
    COORD dwSize;
    COORD dwCursorPosition;
    WORD  wAttributes;
    SMALL_RECT srWindow;
    COORD dwMaximumWindowSize;
} CONSOLE_SCREEN_BUFFER_INFO, *PCONSOLE_SCREEN_BUFFER_INFO;

extern "C" BOOL WINAPI GetConsoleScreenBufferInfo( HANDLE, PCONSOLE_SCREEN_BUFFER_INFO );
extern "C" BOOL WINAPI SetConsoleCursorPosition( HANDLE, COORD );

// error/usage message

void Usage( void )
{
   cerr << "\nProgram: \"ls.exe\" - Windows NT directory list utility - Version 1.0" << endl;
   cerr << "   Copyright (c) Jon S. Whalen 1993. All rights reserved." << endl;
   cerr << "\nusage: ls [-1?alstzLSTFZ] [path|file|pattern]\n" << endl;
   cerr << "   ?|h: display this usage message" << endl;
   cerr << "   1:   format as single column, left adjusted" << endl;
   cerr << "   a:   show all files (including .* and hidden files)" << endl;
   cerr << "   F:   add suffixes to the file names:" << endl;
   cerr << "        '\\' = directory" << endl;
   cerr << "        '!' = hidden file (only when -a is specified)" << endl;
   cerr << "        '&' = system file" << endl;
   cerr << "   l:   use long listing format" << endl;
   cerr << "   L:   use longer listing format" << endl;
   cerr << "   s:   sort by size - decreasing" << endl;
   cerr << "   S:   sort by size - increasing" << endl;
   cerr << "   t:   sort by modification time - newest first" << endl;
   cerr << "   T:   sort by modification time - oldest first" << endl;
   cerr << "   z:   sort by name - lexical order (i.e. alphabetical order)" << endl;
   cerr << "   Z:   sort by name - reverse lexical order" << endl;
   cerr << "\nNotes:\n" << endl;
   cerr << "   1. For compatibility with Unix ls, file and directory names beginning" << endl;
   cerr << "      with '.' are not displayed unless the -a option is specified." << endl;
   cerr << "   2. Specifiying only a drive name (e.g. \"ls C:\") will list the contents" << endl;
   cerr << "      of the current working directory for that drive." << endl;
   cerr << "   3. File systems which are case-insensitive (e.g. FAT) are displayed" << endl;
   cerr << "      in lower case. Case-sensitive file systems (e.g. NTFS) are displayed" << endl;
   cerr << "      in mixed upper and lower case. It is Unicode compatible." << endl;
   cerr << "   4. This program is freely distributable and is provided \"AS-IS\", WITHOUT" << endl;
   cerr << "      WARRANTY of any kind, either express or implied. No guarantee is made" << endl;
   cerr << "      of suitablility for any purpose, whatsoever." << endl;
}

struct FileListEl {
   WIN32_FIND_DATA *lpwfd;
   FileListEl *next, *prev;
   FileListEl( void ) : lpwfd(0), next(0), prev(0) { }
   ~FileListEl( void ) { if( lpwfd ) delete lpwfd; }
};

void SwapEl( FileListEl * cur, FileListEl *next )
{
   FileListEl * p1 = (cur?cur->prev:0);
   FileListEl * p2 = cur;
   FileListEl * p3 = next;
   FileListEl * p4 = (p3?p3->next:0);

   if( p1 ) p1->next = p3;
   p3->prev = p1;
   if( p4 ) p4->prev = p2;
   p2->next = p4;
   p3->next = p2;
   p2->prev = p3;
}

int GetFileList( TCHAR * filter, FileListEl *& head, DWORD & max_filename_len )
{
   FileListEl * cur = 0;
   WIN32_FIND_DATA * lpwfd;
   int matches = 0;
   DWORD len = 0;

   lpwfd = new WIN32_FIND_DATA;
   HANDLE hSearch = FindFirstFile( filter, lpwfd );
   if( hSearch == INVALID_HANDLE_VALUE )
   {
      return 0;
   }

   // else
   cur = head = new FileListEl;
   head->lpwfd = lpwfd;
   max_filename_len = lstrlen(lpwfd->cFileName);
   matches = 1;

   lpwfd = new WIN32_FIND_DATA;
   while( FindNextFile( hSearch, lpwfd ) )
   {
      cur->next = new FileListEl; cur->next->prev = cur; cur = cur->next;
      cur->lpwfd = lpwfd;

      len = lstrlen(lpwfd->cFileName);
      if( max_filename_len < len ) max_filename_len = len;

      lpwfd = new WIN32_FIND_DATA;
      matches++;
   }
   delete lpwfd;

   FindClose( hSearch );

   return matches;
}

int main( int argc, TCHAR **argv )
{
   CONSOLE_SCREEN_BUFFER_INFO csbi;
   TIME_ZONE_INFORMATION tzi;

   TCHAR filter[512]; filter[0] = 0;
   TCHAR cur_directory[512]; cur_directory[0] = 0;
   TCHAR new_directory[512]; new_directory[0] = 0;
   TCHAR * cp = 0;

   DWORD len = 0;
   DWORD maxlen = 512;
   DWORD max_filename_len = 0;
   LPTSTR progname = argv[0];
   FileListEl * head = 0;
   FileListEl * cur = 0;
   DWORD pos = 0;
   int column = 0;
   int num_columns = 0;

   // Flags

   int f_list_all         = 0;
   int f_long_list        = 0;
   int f_modify_time_sort = 0;
   int f_alpha_sort       = 1;
   int f_size_sort        = 0;
   int f_list_with_suffix = 0;
   int f_single_column    = 0;

   int error              = 0;
   int suffixed           = 0;
   int case_sensitive     = 0;
   int matches            = 0;
   long result            = 0;

   // open a handle to the console

   HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

   // check out the environment

   result = GetCurrentDirectory( maxlen, cur_directory );
   result = GetConsoleScreenBufferInfo(hOutput, &csbi);
   result = GetTimeZoneInformation(&tzi);

   DWORD line_length = csbi.dwSize.X;

   // process command line args

   argc--; argv++; // skip progname
   for ( ; argc && ((*argv)[0] == L'/' || (*argv)[0] == L'-'); argv++, argc-- )
   {
      cp = *argv; cp++;
      while( *cp )
      {
         switch( *cp )
         {
            case L'h':
            case L'?':
               Usage();
               return -1;
               break;
            case L'1':
               f_single_column++;
               break;
            case L'a':
               f_list_all++;
               break;
            case L'l':
               f_long_list = 1;
               break;
            case L'L':
               f_long_list = 2;
               break;
            case L's':
               f_size_sort = 1;
               f_alpha_sort = 0;
               f_modify_time_sort = 0;
               break;
            case L'S':
               f_size_sort = -1;
               f_alpha_sort = 0;
               f_modify_time_sort = 0;
               break;
            case L't':
               f_modify_time_sort = 1;
               f_alpha_sort = 0;
               f_size_sort = 0;
               break;
            case L'T':
               f_modify_time_sort = -1;
               f_alpha_sort = 0;
               f_size_sort = 0;
               break;
            case L'F':
               f_list_with_suffix++;
               break;
            case L'z':
               f_alpha_sort = 1;
               f_modify_time_sort = 0;
               f_size_sort = 0;
               break;
            case L'Z':
               f_alpha_sort = -1;
               f_modify_time_sort = 0;
               f_size_sort = 0;
               break;
            default:
               cerr << "ls: illegal option -- " << *cp << endl;
               error++;
         }
         cp++;
      }
   }

   if( error )
   {
      Usage( );
      return -1;
   }

   if( argc == 0 )
   {
      filter[0] = L'*';
      filter[1] = L'.';
      filter[2] = L'*';
      filter[3] = 0;
   }
   else
   {
      // check to see if the argument specifies a path

      lstrcpy( filter, *argv );
      long index = lstrlen( filter );
      switch( filter[index - 1] )
      {
         case L':':
         case L'\\':
            filter[index] = L'*';
            filter[index + 1] = L'.';
            filter[index + 2] = L'*';
            filter[index + 3] = 0;
            break;
      }

   }

   // get drive information

   DWORD dwMaxFilename, dwFlags;
   TCHAR file_system_name[32];
   TCHAR volume_name[128];
   DWORD sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters;
   DWORD free_bytes, total_bytes;

   if( filter[1] == L':' )
   {
      TCHAR tmp[4];
      tmp[0] = filter[0]; tmp[1] = filter[1]; tmp[2] = L'\\'; tmp[3] = 0;
      GetVolumeInformation( tmp, volume_name, 128, 0, &dwMaxFilename, &dwFlags, file_system_name, 32 );
      GetDiskFreeSpace( tmp, &sectors_per_cluster, &bytes_per_sector, &free_clusters, &total_clusters);
   }
   else
   {
      GetVolumeInformation( 0, volume_name, 128, 0, &dwMaxFilename, &dwFlags, file_system_name, 32 );
      GetDiskFreeSpace( 0, &sectors_per_cluster, &bytes_per_sector, &free_clusters, &total_clusters);
   }

   if( dwFlags & FS_CASE_IS_PRESERVED )
      case_sensitive++;
   total_bytes = total_clusters * sectors_per_cluster * bytes_per_sector;
   free_bytes = free_clusters * sectors_per_cluster * bytes_per_sector;

   // first find all matching files

   matches = GetFileList( filter, head, max_filename_len );
   if( !matches )
   {
      cout << filter << " not found." << endl;
      return 0;
   }
   else if( matches == 1 && (head->lpwfd->dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) )
   {
      delete head; head = 0;

      // the filter was a directory name, add \*.* to the filter

      long index = lstrlen( filter );
      filter[index] = L'\\';
      filter[index + 1] = L'*';
      filter[index + 2] = L'.';
      filter[index + 3] = L'*';
      filter[index + 4] = 0;

      // now try again

      matches = GetFileList( filter, head, max_filename_len );
   }

   if( f_single_column )
      num_columns = 1;
   else
      num_columns = line_length/(max_filename_len + 4);

   // now sort

   if( f_modify_time_sort )
   {
      // this is a dumb sorting algorithm -- I'll replace it soon?
      int swapped_one = 1;

      while( swapped_one )
      {
         swapped_one = 0;
         for( cur = head; cur && cur->next; cur = cur->next )
         {
            result = CompareFileTime( &(cur->lpwfd->ftLastWriteTime), &(cur->next->lpwfd->ftLastWriteTime) );

            if( f_modify_time_sort < 0 ) result *= -1;

            if( result == -1 )
            {
               // swap the entries
               swapped_one++;
               FileListEl * next = cur->next;
               SwapEl( cur, next );
               if( head == cur ) head = next;
               cur = next;
            }
         }
      }
   }
   else if( f_size_sort )
   {
      // this is a dumb sorting algorithm -- I'll replace it soon?
      int swapped_one = 1;

      while( swapped_one )
      {
         swapped_one = 0;
         for( cur = head; cur && cur->next; cur = cur->next )
         {
            if( (cur->lpwfd->nFileSizeLow) < (cur->next->lpwfd->nFileSizeLow) )
               result = -1;
            else if ( (cur->next->lpwfd->nFileSizeLow) == (cur->lpwfd->nFileSizeLow) )
               result = 0;
            else
               result = 1;

            if( f_size_sort < 0 ) result *= -1;

            if( result == -1 )
            {
               // swap the entries
               swapped_one++;
               FileListEl * next = cur->next;
               SwapEl( cur, next );
               if( head == cur ) head = next;
               cur = next;
            }
         }
      }
   }
   else if( f_alpha_sort )
   {
      // this is a dumb sorting algorithm -- I'll replace it soon?
      int swapped_one = 1;

      while( swapped_one )
      {
         swapped_one = 0;
         for( cur = head; cur && cur->next; cur = cur->next )
         {
            result = lstrcmp( (cur->next->lpwfd->cFileName), (cur->lpwfd->cFileName) );

            if( f_alpha_sort < 0 ) result *= -1;

            if( result == -1 )
            {
               // swap the entries
               swapped_one++;
               FileListEl * next = cur->next;
               SwapEl( cur, next );
               if( head == cur ) head = next;
               cur = next;
            }
         }
      }
   }

   // print the listing

   if ( f_long_list )
   {
      if( f_long_list > 1 )
      {
         cout << "\nVolume Name: \"" << volume_name << "\"  File System Type: [" << file_system_name << "]" << endl;
         cout << "\n[Attribs]    [Size]      [Created]       [Last Modified]   [Name]" << endl;
      }
      else
         cout << "\n[Attribs]    [Size]   [Last Modified]   [Name]" << endl;

      for( cur = head; cur; cur = cur->next )
      {
         if ( f_list_all || (cur->lpwfd->cFileName[0] != L'.' && !(cur->lpwfd->dwFileAttributes&FILE_ATTRIBUTE_HIDDEN)) )
         {
            SYSTEMTIME stm;
            TCHAR buffer[128];

            // attributes

            cout << (CHAR)((cur->lpwfd->dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) ? 'd':'-');
            cout << (CHAR)((cur->lpwfd->dwFileAttributes&FILE_ATTRIBUTE_HIDDEN)    ? 'h':'-');
            cout << (CHAR)((cur->lpwfd->dwFileAttributes&FILE_ATTRIBUTE_SYSTEM)    ? 's':'-');
	    cout << (CHAR)((cur->lpwfd->dwFileAttributes&FILE_ATTRIBUTE_READONLY)  ? 'r':'-');
	    cout << (CHAR)((cur->lpwfd->dwFileAttributes&FILE_ATTRIBUTE_ARCHIVE)   ? 'a':'-');

	    // attributes for NTFS only

	    cout << (CHAR)((cur->lpwfd->dwFileAttributes&FILE_ATTRIBUTE_TEMPORARY)     ? 't':'-');
	    cout << (CHAR)((cur->lpwfd->dwFileAttributes&FILE_ATTRIBUTE_ATOMIC_WRITE)  ? 'm':'-');
	    cout << (CHAR)((cur->lpwfd->dwFileAttributes&FILE_ATTRIBUTE_XACTION_WRITE) ? 'x':'-');
	    cout << " ";

	    // size

	    wsprintf( buffer, "%wc%9d ", ((cur->lpwfd->nFileSizeHigh)?L'+':L' '), cur->lpwfd->nFileSizeLow );
	    cout << buffer;

            if( f_long_list > 1 )
            {
               // creation time

	       FileTimeToSystemTime( &(cur->lpwfd->ftCreationTime), &stm);
	       wsprintf( buffer, " %02d-%02d-%02d %02d:%02d:%02d ", stm.wMonth, stm.wDay, (stm.wYear%100), (stm.wHour - tzi.Bias/60), stm.wMinute, stm.wSecond );
	       cout << buffer;

               // last access time

	       //FileTimeToSystemTime( &(cur->lpwfd->ftLastAccessTime), &stm);
	       //wsprintf( buffer, " %02d-%02d-%02d %02d:%02d:%02d ", stm.wMonth, stm.wDay, (stm.wYear%100), (stm.wHour - tzi.Bias/60), stm.wMinute, stm.wSecond );
	       //cout << buffer;
            }

	    // last modification time

	    FileTimeToSystemTime( &(cur->lpwfd->ftLastWriteTime), &stm);
	    wsprintf( buffer, " %02d-%02d-%02d %02d:%02d:%02d  ", stm.wMonth, stm.wDay, (stm.wYear%100), (stm.wHour - tzi.Bias/60), stm.wMinute, stm.wSecond );
	    cout << buffer;

	    cout << (case_sensitive?cur->lpwfd->cFileName:CharLower(cur->lpwfd->cFileName));
	    if( f_list_with_suffix )
	    {
               if(cur->lpwfd->dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)
                  cout << L'\\';
	       else
	       {
                  if(cur->lpwfd->dwFileAttributes&FILE_ATTRIBUTE_HIDDEN)
                     cout << L'!';
                  if(cur->lpwfd->dwFileAttributes&FILE_ATTRIBUTE_SYSTEM)
                     cout << L'&';
               }
            }
	    cout << endl;
	 }
      }

      if( f_long_list > 1 )
      {
         cout << "\nVolume Statistics:" << endl;
         cout << "\n[Size] " << total_bytes << "  [Free] " << free_bytes <<"  [% Used] " << 100.00*(long)(total_bytes - free_bytes)/(long)total_bytes << "." << endl;
         //cout << "bytes used:  " << (total_bytes - free_bytes)  endl;
      }
   } // end-if

   else
   for( cur = head; cur; cur = cur->next )
   {
      if ( f_list_all || (cur->lpwfd->cFileName[0] != L'.' && !(cur->lpwfd->dwFileAttributes&FILE_ATTRIBUTE_HIDDEN)) )
      {
         len = lstrlen(cur->lpwfd->cFileName);
         if( column == num_columns )
         {
            cout << endl;
            column = 1;
         }
         else
            column++;

         // filename

         cout << (case_sensitive?cur->lpwfd->cFileName:CharLower(cur->lpwfd->cFileName));
         pos += len;

         // suffix

         suffixed = 0;
         if( f_list_with_suffix )
         {
            if(cur->lpwfd->dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)
            {
               cout << L'\\';
               suffixed++;
            }
            else
            {
               if(cur->lpwfd->dwFileAttributes&FILE_ATTRIBUTE_HIDDEN)
               {
                  cout << L'!';
                  suffixed++;
               }
               if(cur->lpwfd->dwFileAttributes&FILE_ATTRIBUTE_SYSTEM)
               {
                  cout << L'&';
                  suffixed++;
               }
            }
            pos++;
         }

         // padding

         len = max_filename_len - len + 4 - suffixed;
         if( column < num_columns )
            while( len-- ) cout << ' ';
      }
   }

   return 0;
}
