#include <windows.h>
#include <tchar.h> 
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <list>
#include <vector>
#include <string>
#include <map>
#include <algorithm>
#include <sys/stat.h>
#include <sys/types.h>
#include <io.h>
#include <iomanip>
#include <direct.h>
#include "str.h"
#include "st.h"
#include "file.h"
//#include "wstream.h"

using namespace std;
using namespace string_tools;
using namespace io_tools;

bool is_script_running();

#ifndef NO_MAIN
bool is_script_running()
{
   return true;
}
#endif

#ifdef NO_MAIN
extern "C" {
extern int GetWinHeightCount();
extern int GetWinWidthCount();
}
#else
inline
int GetWinHeightCount()
{
   return 25;
}
inline
int GetWinWidthCount()
{
   return 80;
}
#endif

bool is_dir(const string & file)
{
   bool ret = false;
   File f(file);
   ret = f.is_directory();
   return ret;
}

inline
string clean(const string & str) {
   string ret;
   char ch;
   for(size_t i = 0; i < str.size(); i++) {
      ch = str[i];
      if(isspace(ch) || ispunct(ch) || isalnum(ch)) {
         ret += ch;
      }
   }
   return ret;
}

class LsFile {
   string file_name;
   string the_date;
   string the_time;
   long size;
public:
   bool is_dir;
   bool read_only;
   bool write_only;
   bool is_exe;
   DWORD attr;
   LsFile() {
      attr = 0;
      is_exe = false;
      read_only = false;
      is_dir = false;
      size = 0L;
      write_only = false;
   }
   LsFile(const string & file_name,
          const long size,
          const string & date,
          const string & time,
          const bool is_dir,
          const bool read_only,
          const bool write_only,
          const bool is_exe,
          DWORD attr) {
      this->attr = attr;
      this->file_name = file_name;
      this->the_date = date;
      this->the_time = time;
      this->size = size;
      this->is_dir = is_dir;
      this->read_only = read_only;
      this->is_exe = is_exe;
      this->write_only = write_only;
   }
   LsFile(const LsFile & ls) {
      file_name = ls.file_name;
      the_date = ls.the_date;
      the_time = ls.the_time;
      size = ls.size;
      is_dir = ls.is_dir;
      read_only = ls.read_only;
      is_exe = ls.is_exe;
      attr = ls.attr;
      write_only = ls.write_only;
   }
   LsFile & operator=(const LsFile & ls) {
      if(this != &ls) {
         file_name = ls.file_name;
         the_date = ls.the_date;
         the_time = ls.the_time;
         size = ls.size;
         is_dir = ls.is_dir;
         read_only = ls.read_only;
         is_exe = ls.is_exe;
         attr = ls.attr;
         write_only = ls.write_only;
      }
      return *this;
   }
   const string & get_name() {
      return file_name;
   }
   const string & get_date() {
      return the_date;
   }
   const string & get_time() {
      return the_time;
   }
   const long get_size() {
      return size;
   }

   bool operator<(const LsFile & cp) {
      bool ret = false;
      ret = file_name < cp.file_name;
      return ret;
   }
};

extern "C" {
extern int ls_main(int argc, char **argv);
}

inline
void
ListAll(list< LsFile > & the_list)
{
   for(list< LsFile >::iterator ptr = the_list.begin(); ptr != the_list.end() && is_script_running(); ptr++) {
      stringstream str,str2;
      string first = "----";
      if(ptr->attr & FILE_ATTRIBUTE_HIDDEN)
         first = "hid-";
      else if(ptr->attr & FILE_ATTRIBUTE_SYSTEM)
         first = "sys-";
      else if(ptr->attr & FILE_ATTRIBUTE_TEMPORARY)
         first = "tmp-";
      else if(ptr->attr & FILE_ATTRIBUTE_COMPRESSED)
         first = "cab-";
      else if(ptr->attr & FILE_ATTRIBUTE_ENCRYPTED)
         first = "enc-";
      else if(ptr->attr & FILE_ATTRIBUTE_SPARSE_FILE)
         first = "spr-";
      else if(ptr->attr & FILE_ATTRIBUTE_REPARSE_POINT)
         first = "sym-";
      else if(ptr->attr & FILE_ATTRIBUTE_DIRECTORY)
         first = "dir-";
      else if(ends_with(ptr->get_name(), ".exe"))
         first = "exe-";
      else 
         first = "fil-";
         
      str << first
            << ((ptr->read_only)?("-"):("w"))
            << ((ptr->is_exe)?("x"):("-"))
            << " " << setw(8) << left << ptr->get_size()
            << " " << setw(10) << left << ptr->get_date()
            << " "  << setw(11) << left << ptr->get_time()
            << " " << ptr->get_name();
      cout << clean(str.str()) << endl;
   }
}

inline
void
ListTable(list< LsFile > & the_list)
{
   int width = 0;
   for(list< LsFile >::iterator ptr = the_list.begin(); ptr != the_list.end(); ptr++) {
      if(ptr->get_name().size() > width) {
         width = ptr->get_name().size();
      }
   }
   int n;
   width++;
   if(width > GetWinWidthCount()) 
      n = 1;
   else 
      n = GetWinWidthCount() /  width;
   vector< vector< string > > tbl;
   int z = 0;
   for(int i = 0; z < the_list.size(); i++) {
      tbl.push_back(vector< string >());
      for(int j = 0; j < n; j++) {
         tbl[i].push_back("");
         z++;
      }
   }
   list< LsFile >::iterator ptr = the_list.begin();
   for(int i = 0; i < n && ptr != the_list.end(); i++) {
      for(int j = 0; j < tbl.size() && ptr != the_list.end(); j++, ptr++) {
         stringstream str;
         str << setw(width) << left << ptr->get_name();
         tbl[j][i] = str.str();
      }
   }
   for(int i = 0; i < tbl.size() && is_script_running(); i++) {
      for(int j = 0; j < tbl[i].size() && is_script_running(); j++) {
         cout << clean(tbl[i][j]);
      }
      cout << endl;
   }
}

void ListOneLine(list< LsFile > & the_list)
{
   for(auto ptr = the_list.begin(); ptr != the_list.end() && is_script_running(); ptr++) {
      cout << clean(ptr->get_name()) << endl;
   }
}

#ifdef NO_MAIN
extern int System0(const string & cmd);
#define System System0
#else
inline
int
System(const string & cmd) {
   return system(cmd.c_str());
}
#endif

void Recursive0(string & file, list< string > & output) 
{
   WIN32_FIND_DATA ffd;
   TCHAR szDir[MAX_PATH];
   HANDLE hFind = INVALID_HANDLE_VALUE;
   map< string, bool > the_map;

   memset(szDir, 0, MAX_PATH);
   strcpy(szDir, file.c_str());
   strcat(szDir,"\\*");

   hFind = FindFirstFile(szDir, &ffd);

   if(INVALID_HANDLE_VALUE == hFind) {
      cerr << "file not found...\n";
      return;
   } 
   do {
      if(strcmp(ffd.cFileName, ".") == 0
         || strcmp(ffd.cFileName, "..") == 0) {
         continue;
      }
      if(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
         string temp = file + string("\\") + string(ffd.cFileName);
         if(the_map.find(temp) == the_map.end()) {
            the_map[temp] = true;
            Recursive0(temp, output);
         }
      }
      else {
         output.push_back(file + string("\\") + string(ffd.cFileName));
      }
   }
   while(FindNextFile(hFind, &ffd) != 0 && hFind != 0 && is_script_running());

   if(INVALID_HANDLE_VALUE != hFind && hFind != 0) {
      FindClose(hFind);
   }
   //System("cmd /c \"dir " + file_array[i] + " /s /b /a-d\"");
}

bool compare_string(const string & arg1, const string & arg2)
{
   bool ret = false;
   if(arg1 > arg2) {
      ret = true;
   }
   return ret;
}
void Recursive(vector< string > & file_array) 
{
   list< string > output;
   char buffer[MAX_PATH];
   char * lp = 0;
   string temp;
   for(size_t i = 0; i < file_array.size() && is_script_running(); i++) {
      if(file_array[i].find(":\\") == string::npos) {
         memset(buffer, 0, MAX_PATH);
         GetFullPathName(file_array[i].c_str(), MAX_PATH-1, buffer, &lp);
         temp = buffer;
      }
      else {
         temp = file_array[i];
      }
      Recursive0(temp, output);
   }
   output.sort(compare_string);
   for(auto ptr = output.begin(); ptr != output.end() && is_script_running(); ptr++) {
      cout << clean(*ptr) << endl;
   }
}

#ifdef NO_MAIN
int ls_main(int argc, char **argv)
#else
int main(int argc, char ** argv)
#endif
{
   map< string, bool > the_map;
   WIN32_FIND_DATA ffd;
   TCHAR szDir[MAX_PATH];
   size_t length_of_arg;
   HANDLE hFind = INVALID_HANDLE_VALUE;
   DWORD dwError=0;
   //struct _stat buf;
   list< LsFile > the_list;
         
   char * temp_argv[2];
   temp_argv[0] = (char*)"ls.exe"; 
   temp_argv[1] = (char*)".";

   if(argc == 1) {
      argv = temp_argv;
      argc = 2;
   }

   vector< string > file_list;
   bool found = false;
   bool show_all = false;
   bool show_long = false;
   bool recursive = false;
   bool one_line = false;
   for(int i = 1; i < argc; i++) {
      if(strchr(argv[i], '-') == argv[i]) {
         int sz = strlen(argv[i]);
         for(int j = 1; j < sz; j++) {
            switch(argv[i][j]) {
            case 'a':
               show_all = true;
            break;
   
            case 'l':
               show_long = true;
            break;

            case 'R':
               recursive = true;
            break;

            case '1':
               one_line = true;
            break;
            }
         }
      }
      else {
         file_list.push_back(argv[i]);
      }
   }

   if(file_list.size() == 0) {
      file_list.push_back(".");
   }

   int ret = 0;

   for(size_t index = 0; index < file_list.size() && is_script_running(); index++) {
	   string* ptr = &file_list[index];
      length_of_arg = ptr->size();

      if(length_of_arg > (MAX_PATH - 3)) {
         cout << "Directory path is too long.\n";
         ret = 1;
         break;
      }

      if(ptr->size()) {
         if((*ptr)[ptr->size()-1] == '\\') {
            string temp = (*ptr) + ".";
			   (*ptr) = temp;
         }
      }
	   memset(szDir, 0, MAX_PATH);
      strcpy(szDir,ptr->c_str());
      if(strchr(szDir, '*') == 0 && is_dir(szDir)) {
         strcat(szDir,"\\*");
      }

      hFind = FindFirstFile(szDir, &ffd);

      if(INVALID_HANDLE_VALUE == hFind) {
         cerr << "file not found...\n";
         ret = 1;
         break;
      } 
 
      int name_width = 15;
      int date_width = 30;
      int size_width = 10;
      int attribute_width = 5;

      bool read_only = false;
      bool write_only = false;
      bool is_dir = false;
      bool is_exe = false;

      string fmt;
      do {
         read_only = is_dir = is_exe = false;

         if(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            is_dir = true;
         }
		
         if(ffd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
            read_only = true;
         }

         if(strstr(to_lower(ffd.cFileName).c_str(), ".exe") != NULL
            || strstr(to_lower(ffd.cFileName).c_str(), ".bat") != NULL) {
            is_exe = true;
         }

         FILETIME ft;
         SYSTEMTIME st;
         char szLocalDate[255], szLocalTime[255];

         FileTimeToLocalFileTime(&ffd.ftLastWriteTime, &ft);
         FileTimeToSystemTime(&ft, &st);
         GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, "MMM' 'dd', 'yyyy",
         szLocalDate, 255);
         GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, "hh:mm:ss tt", szLocalTime, 255);

         DWORD attr = GetFileAttributes(ffd.cFileName);

         LsFile ls_file(ffd.cFileName, 
                        ffd.nFileSizeLow, 
                        szLocalDate, 
                        szLocalTime,
                        is_dir, 
                        read_only, 
                        write_only,
                        is_exe,
                        attr); 
         if(the_map.find(ffd.cFileName) == the_map.end()) {
            the_map[ffd.cFileName] = true;
            the_list.push_back(ls_file); 
         }
      }
      while(FindNextFile(hFind, &ffd) != 0 && hFind != 0 && is_script_running());
      if (INVALID_HANDLE_VALUE != hFind && hFind != 0) {
         FindClose(hFind);
      }
      the_list.sort();
      if(recursive) {
         Recursive(file_list);
      }
      else if(one_line) {
         ListOneLine(the_list);
      }
      else if(show_all) {
         ListAll(the_list);
      }
      else {
         ListTable(the_list);
      }
   }
   cout.flush();
   return ret;
}
