/*

CLASS {File_path|_Gcc},
NATIVE, UNTESTED, STOPPING, RECURSIVE :

IMPORT: {String_functions}, {System_functions}, {Date_time} ;

NATIVE: @
        "#include <sys/stat.h>"
        "#include <stdio.h>"
        "#ifdef MSVSTUDIO"
        "	#include <io.h>"
        "	#include <time.h>"
        "#else"
        "	#include <unistd.h>"
        "	#include <dirent.h>"
        "#endif"
        .

FUNCTION Exists|_file( STR Path|_of_file) ==> BOOL, NATIVE :
	        CASE Path == "" ? ==> ;
	        NATIVE     
   @"if (X_Path.length() == 0) return false;"
    "#ifdef MSWINDOWS"
    "    int siz = MultiByteToWideChar(CP_UTF8, 0, "
    "        X_Path.c_str(), X_Path.length(), NULL, 0);"
    "    std::wstring w;"
    "	 w.reserve(siz);"
    "	 for (int i = 0; i < siz; i++) w += '_';"
    "    MultiByteToWideChar(CP_UTF8, 0, X_Path.c_str(), "
    "           X_Path.length(), &w[0], siz);"
    "       WIN32_FIND_DATAW fd;"
    "       HANDLE find = FindFirstFileW(w.c_str(), &fd);"
    "       if (find == INVALID_HANDLE_VALUE) return false;"
    "       FindClose(find);"
    "       RESULT = (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)== 0;"
    "#else"
    "		struct stat sb;"
    "		const char *path = X_Path.c_str();"
    "       int exists =  stat(path, &sb);"
    "       if (exists == -1) return false;"
    "		switch (sb.st_mode & S_IFMT)"
    "		{"
    ''		case S_IFREG: case S_IFLNK: RESULT = true; break;''
    "		}"
    "#endif" .

FUN Delete_file(STR Path|_file) :
    CASE !Path.Exists ? ==> ;
    CASE TEST && "test" !IN Path.Lower ?
         STOP "In test mode, only TEST files can be deleted" ;
    Path.delete .

FUN delete|_file(STR Path|_file), NATIVE :
   @"try {"
    "   #ifdef MSWINDOWS"
    "         DeleteFileA(&X_Path[0u]);"
    "	#else"
    ''		  unlink(&X_Path[0]);''
    "   #endif"
    "} catch (std::exception& e) {"
    "     AL4_system::Any_exception(e);"
    "}" .

FUN Rename|_file( STR Path|_file, STR New|_name) :
    CASE !Path.Exists ? ==> ;
    CASE TEST && ("test" !IN Path.Lower || "test" !IN New.Lower)?
         STOP "In TEST mode only TEST files can be renamed "
              "and only to TEST places"
    ;
    CASE New.Exists ? New.Delete_file ;
    rename(Path, New) .

FUN rename|_file(STR Path|_file, STR New|_name), NATIVE :
   @"try {"
    "	 #ifdef MSWINDOWS"
    "           MoveFileA(&X_Path[0], &X_New[0]);"
    "	 #else"
    ''			rename(&X_Path[0], &X_New[0]);''
    "    #endif"
    "} catch (std::exception& e) {"
    "     AL4_system::Any_exception(e);"
    "}" .

FUN Move_file(STR Path|_file, STR New|_path) :
    Rename(Path, New) .

FUN Copy_file(STR S|ource_file_path, STR D|estination) :
    CASE !S.Exists ? ==> ;
    CASE !D.Extract_path.Directory_exists ? ==> ;
    Stop_if_test(S)
    Stop_if_test(D)
    CASE D.Exists ? Delete_file(D) ;
    do_copy_file(S, D) .

FUN do_copy_file(STR S|ource_file_path, STR D|estination),
        NATIVE :
   @"    try {"
    "	 #ifdef MSWINDOWS"
    "        CopyFileA(X_S.c_str(), X_D.c_str(), true);"
    "	 #else"
    ''		execl("/bin/cp", "-p", &X_S[0], &X_D[0], (char*)0);''
    "    #endif"
    "} catch (std::exception& e) {"
    "     AL4_system::Any_exception(e);"
    "}" .


FUN Dir_exists||Directory_exists(
STR Path|_of_dir ) ==> BOOL
	:
    STR path|_fixed = Path
    CASE path[*] IN "\/" ? path = path[0 TO *-1] ;
    RESULT = do_Directory_exists(path) .

FUN do_Directory_exists(STR Path|_of_dir) ==> BOOL, NATIVE :
   @"	 #ifdef MSWINDOWS"
    "       WIN32_FIND_DATAA fd;"
    "       HANDLE find = FindFirstFileA(&X_Path[0u], &fd);" 
    "    	if (find == INVALID_HANDLE_VALUE) return false;"
    "    	FindClose(find);"
    "    	RESULT = (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;"
    "	 #else"
    "		struct stat sb;"
    "		const char *path = X_Path.c_str();"
    "       int exists =  stat(path, &sb);"
    "       if (exists == -1) return false;"
    ''		RESULT = (sb.st_mode & S_IFMT) == S_IFDIR;''
    "	 #endif" .

FUNCTION Create_dir|ectory(STR Path|_to_new_dir),
RECURSIVE
	:
	CASE Path.Trim == "" ? ==> ;
	STR parent|_directory = Path.Remove_trailing(Path_separator).Extract_path
	CASE !parent.Dir_exists ? parent.Create_dir ;
	do_Create_dir(Path) .
FUN do_Create_dir(STR Path|_to_directory), NATIVE : @
    "	 #ifdef MSWINDOWS"
    "           CreateDirectoryA(&X_Path[0], NULL);"
    "	 #else"
    ''	        mkdir(&X_Path[0], S_IRWXU | S_IRWXG | S_IRWXO);''
    "    #endif" .

FUN Catalog|ue_of_directory(STR Dir|ectory_path,
        STR Cat|alogue_scanned[]), NATIVE :
   @"	 #ifdef MSWINDOWS"
    "           WIN32_FIND_DATAA fd;"
    "           std::string path = X_Dir + " #QU "\\*" #QU ";"
    "           HANDLE find = FindFirstFileA(&path[0], &fd);"
    "           bool next_found = find != INVALID_HANDLE_VALUE;"
    "	 #else"
    "			DIR *dirp = opendir(&X_Dir[0]);"
    "			dirent* ent = nullptr;"
    "			if (dirp) ent = readdir(dirp);"
    "			bool next_found = ent != nullptr;"
    "    #endif"
    "    X_Cat->Clear();"
    "    while (next_found)"
    "    {"
    "		   #ifdef MSWINDOWS"
    "          	   std:: string fname = fd.cFileName;"
    ''             if (fname != "." && fname != "..") {''
    "					if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)"
    "						!= 0) X_Cat->Add(fname + '\\');"
    "              		else X_Cat->Add(fname);"
    "			   }"
    "              next_found = FindNextFileA(find, &fd);"
    "		   #else"
    ''			   if (0!=strcmp(ent->d_name, ".") ''
    ''			    && 0!=strcmp(ent->d_name, "..")) {''
    "					if (ent->d_type == DT_DIR)"
    "						 X_Cat->Add(ent->d_name + '\\');"
    "					else X_Cat->Add(ent->d_name);"
    "			   }"
    "			   ent = readdir(dirp);"
    "			   next_found = ent != nullptr;"
    "          #endif"
    "    };"
    "    #ifdef MSWINDOWS"
    "           FindClose(find);"
   	"	 #else"
   	"			if (dirp) closedir(dirp);"
    "    #endif"
    .
    
ENUM {file_attr|ibutes} :
    'READONLY   |_FILE',
    'HIDDEN     |_FILE_DIRECTORY',
    'SYSTEM     |_FILE_DIRECTORY',
    'DIRECTORY  |_NOT_A_FILE',
    'ARCHIVE    |_FILE',
    'NORMAL     |_FILE',
    'TEMPORARY  |_FILE',
    'COMPRESSED |_FILE',
    'OFFLINE    |_FILE' .
    
STRUCTURE {file_detail|ed_info} :
        	//STR  File_name
            BOOL Attrib|utes[{file_attr}]
            REAL Size|_in_bytes
            {date_time} Creation|_date_time
            {date_time} Modified|_date_time
            {date_time} Accessed|_date_time .
            
FUN Catalog_detailed( STR Dir|ectory_path,
    STR Cat|alogue_scanned[], {file_detail} Det|ails_for_files[])
    :
	Cat[].Clear
	Det[].Clear
	Catalog(Dir, Cat[])
	STR path|_trailed_slash = Dir.Trailing_slash
	--------------------- 'getting details on each file found'
	FOR s IN Cat[] :
		Det[] << File_detail(path s.Remove_trailing(Path_separator))
	; .
	
FUNCTION File_detail|ed_info(
STR Path|_to_file ) ==> {file_detail}
	:
	//RESULT = {file_detail}(File_name = Path.Extract_file_name)
    CASE Path == "" ? ==> ;
    STR attr|ibutes[]
    REAL times|_of_file[]
    RESULT.Size = native_File_details(Path, attr[], times[])
    -------------------------------- 'times'
    RESULT.Creation.DT = times[0]
    RESULT.Modified.DT = times[1]
    RESULT.Accessed.DT = times[2]
    -------------------------------- 'attribs'
    RESULT.Attrib['READONLY'] = "R" IN attr[0] 
    RESULT.Attrib['HIDDEN'] = "H" IN attr[0] 
    RESULT.Attrib['SYSTEM'] = "S" IN attr[0] 
    RESULT.Attrib['DIRECTORY'] = "D" IN attr[0] 
    -------------------------------- 'exotic attribs'
    RESULT.Attrib['ARCHIVE'] = "A" IN attr[0] 
    RESULT.Attrib['NORMAL'] = "N" IN attr[0] 
    RESULT.Attrib['TEMPORARY'] = "T" IN attr[0] 
    RESULT.Attrib['COMPRESSED'] = "C" IN attr[0] 
    RESULT.Attrib[{file_attr}.'OFFLINE'] = "O" IN attr[0] 
    .
    FUN native_File_details(STR Path|_of_file,
    	STR attr|ibutes_put_in_0[], REAL times|_of_file[]) ==> REAL,
        NATIVE : @
    ''if (X_Path != "")''
    "{"
    "  std::string a = "#QU#QU";"
    "#ifdef MSWINDOWS"
    "       WIN32_FIND_DATAA fd;"
    "       HANDLE find = FindFirstFileA(&X_Path[0], &fd);"
    "       bool is_found = find != INVALID_HANDLE_VALUE;"
    "       if (is_found)"
    "           FindClose(find);"
    "     std::string temp_s = X_Path;"
    "     int fa = fd.dwFileAttributes;"
    "	  if ((fa & FILE_ATTRIBUTE_READONLY) != 0) a += 'R';"
    "     if ((fa & FILE_ATTRIBUTE_HIDDEN) != 0) a += 'H';"
    "     if ((fa & FILE_ATTRIBUTE_SYSTEM) != 0) a += 'S';"
    "     if ((fa & FILE_ATTRIBUTE_ARCHIVE) != 0) a += 'A';"
    "     if ((fa & FILE_ATTRIBUTE_NORMAL) != 0) a += 'N';"
    "     if ((fa & FILE_ATTRIBUTE_TEMPORARY)!= 0) a += 'T';"
    "     if ((fa & FILE_ATTRIBUTE_COMPRESSED)!=0) a += 'C';"
    "     if ((fa & FILE_ATTRIBUTE_OFFLINE) != 0) a += 'O';"
    "     if ((fa & FILE_ATTRIBUTE_DIRECTORY)!= 0) a += 'D';"
    "     else"
    "     {"
    "      struct stat sbuf;"
    "      int rc = stat(temp_s.c_str(), &sbuf);"
    "      if (rc == 0)"
    "	   {"
    "            RESULT = sbuf.st_size;"
    "            tm* Tm;"
    "            Tm = localtime(&sbuf.st_ctime);"
    "            X_times->Add(mktime(Tm) * 1000.0);"
    "            Tm = localtime(&sbuf.st_mtime);"
    "            X_times->Add(mktime(Tm) * 1000.0);"
    "            Tm = localtime(&sbuf.st_atime);"
    "            X_times->Add(mktime(Tm) * 1000.0);"
    "      }"
    "     }"
    "#else //------------------------------- LINUX"
    "       uid_t user_uid = getuid();"
    "       gid_t group_uid = getgid();"
    "     struct stat sbuf;"
    "     std::string f_path = X_Path;"
    "     int rc = stat(f_path.c_str(), &sbuf);"
    "     if (rc == 0)"
    "     {"
    "         if (f_path[0] == '.') a += 'H';"
    "         if (sbuf.st_uid == user_uid)"
    "         {"
    "            if ((sbuf.st_mode & S_IWUSR) == 0) a += 'R';"
    "         }"
    "         else if (sbuf.st_gid == group_uid)"
    "         {"
    "            if ((sbuf.st_mode & S_IWGRP) == 0) a += 'R';"
    "         }"
    "         if ((sbuf.st_mode & S_IFDIR) != 0) a += 'D';"
    "		  else {"
    "         	RESULT = sbuf.st_size;"
    "         	tm* Tm;"
    "         	Tm = localtime(&sbuf.st_ctime);"
    "         	X_times->Add(mktime(Tm) * 1000.0);"
    "         	Tm = localtime(&sbuf.st_mtime);"
    "         	X_times->Add(mktime(Tm) * 1000.0);"
    "         	Tm = localtime(&sbuf.st_atime);"
    "         	X_times->Add(mktime(Tm) * 1000.0);"
    "		  }"
    "     }"
    "#endif"
    "  X_attr->Add(a);"
    "}"
    .

FUNCTION Change_ext|ension(STR Path|_of_file,
    STR New_ex|tension) ==> STR
    :
    ----------------------------------------- 'Find a dot from the end'
    FOR i IN [Path.Len-1 DOWNTO 0] :
        CASE Path[i] ?
            ["."]: RESULT = Path[0 TO i-1] New_ex ==>
            ["\", "/"]: BREAK i ;
    ;
    ----------------------------------------- 'Not found ? Just append'
    RESULT = Path New_ex .
    
FUN Ext|ension|_of_file(STR Path|_of_file) ==> STR :
	STR no_ext|ension = Path.Change_ext("")
	RESULT = Path[no_ext.Len TO *] .
    

FUNCTION Extract_path( STR Path|_of_file_full) ==> STR :
    FOR i IN [Path.Len-1 DOWNTO 0] :
        CASE Path[i] IN ":\/" ? RESULT = Path[0 TO i] ==> ; 
    ; .

FUNCTION Extract_file_name( STR Path|_file) ==> STR :
    FOR i IN [Path.Len-1 DOWNTO 0] :
        CASE Path[i] IN ":\/" ? RESULT = Path[i+1 TO *] ==> ;
    ;
    RESULT = Path .

FUNCTION Get_file_time_age_seconds(
STR Path|_file) ==> REAL,
DEPRECATED('use only for compatibility with the primary AL4 compiler')
	:
	{file_detail} fi|nformation = File_detail(Path)
	RESULT = fi.Modified.DT / 1000 .
	
FUNCTION Get_file_modified_time_formatted(
STR Path|_file) ==> STR,
DEPRECATED('use only for compatibility with the primary AL4 compiler')
	:
	{file_detail} fi|nformation = File_detail(Path)
	
	RESULT = fi.Modified.Year.S_fixed_z(4) fi.Modified.Month.S_fixed_z(2)
			 "" fi.Modified.Day.S_fixed_z(2) 
			 " " fi.Modified.Hour.S_fixed_z(2)
			 "" fi.Modified.Minute.S_fixed_z(2)
			 "" fi.Modified.Second.S_fixed_z(2) .
			 
FUN S_fixed_z|eroes_leading(
INT N|umber_to_format, INT D|igits_minimum) ==> STR
	:
	RESULT = N.S
	FOR i|dummy IN [RESULT.Len+1 TO D] : RESULT = "0" RESULT ; .

//FUN Set_file_time_age(
//STR Path|_file, {Date_time}.{date_time} Age|_2_seconds_recision)
//	:
//	do_Set_file_time_age(Path, Age.DT) .
	//FUN do_Set_file_time_age(
	//STR Path|_file, REAL Age|_2_seconds_recision), NATIVE,
	//EPRECATED('Platform dependant') :
	//CASE !Path.Exists ? ==> ;
	//ATIVE
	//	"try {"
	//"	File.SetLastWriteTime(X_Path, DateTime.FromOADate("
	//"		X_Age / 24.0 / 3600000));"
	//"}"
	//"catch {"
	//"}"	.
	
//FUN Set_file_attributes(
//STR Path|_file, STR Attr|ibutes_Hidden_Readonly_System), NATIVE,
//DEPRECATED('Platform dependent') :
//	CASE !Path.Exists ? ==> ;
//	BOOL hidden|_file = "H" IN Attr
//	BOOL readonly = "R" IN Attr
//	BOOL system|_file = "S" IN Attr
//NATIVE @
//"	FileAttributes fa = FileAttributes.Normal;"
//"	if (X_hidden) fa = fa | FileAttributes.Hidden;"
//"	if (X_readonly) fa = fa | FileAttributes.ReadOnly;"
//"	if (X_system) fa = fa | FileAttributes.System;"
//"	File.SetAttributes(X_Path, fa);" .
			
			
	
FUN Format_size(REAL Size|_in_bytes) ==> STR :
	CASE ?
	[Size < 1024] ? RESULT = Size.Int.S " Bytes"
	[Size < 1024 * 1024 * 0.9] ? RESULT = (Size/1024).S 
		----------------------------- 'rest 1 decimal digit'
		RESULT = RESULT.Replace(",", ".")
		CASE "." IN RESULT ? RESULT = RESULT[0 TO RESULT.Find(".")+1] ;
		RESULT << " KBytes"
	[Size < 1024.0 * 1024 * 1024 * 0.9] ? RESULT = (Size/1024/1024).S
		----------------------------- 'rest 2 decimal digit', REUSED
		RESULT = RESULT.Replace(",", ".")
		CASE "." IN RESULT ? RESULT = RESULT[0 TO RESULT.Find(".")+2] ;
		----------------------------- 'end'
		RESULT << " MBytes"
	[Size < 1024.0 * 1024 * 1024 * 1024 * 0.75] ?
		RESULT = (Size/1024/1024/1024).S
		LIKE ........................ 'rest 2 decimal digit'
		RESULT << " GBytes"
	[Size < 1024.0 * 1024 * 1024 * 1024 * 1024 * 0.75] ?
		RESULT = (Size/1024/1024/1024/1024).S
		LIKE ........................ 'rest 2 decimal digit'
		RESULT << " TBytes"
	[Size < 1024.0 * 1024 * 1024 * 1024 * 1024 * 1024 * 0.75] ?
		RESULT = (Size/1024/1024/1024/1024/1024).S
		LIKE ........................ 'rest 2 decimal digit'
		RESULT << " PBytes"
	ELSE
		RESULT = (Size/1024/1024/1024/1024/1024/1024).S
		LIKE ........................ 'rest 2 decimal digit'
		RESULT << " HBytes" ; .
		
TEST of_Format_size :
	ASSERT Format_size(999) == "999 Bytes", Format_size(999)
	ASSERT Format_size(1999) == "1.9 KBytes", Format_size(1999)
	ASSERT Format_size(2550) == "2.4 KBytes", Format_size(2550)
	ASSERT Format_size(3000_000.0) == "2.86 MBytes", Format_size(3000_000.0)
	----------------------------------- 'even bigger'
	ASSERT Format_size(4_000_000_000.0) == "3.72 GBytes",
		   Format_size(4_000_000_000.0)
		   
	ASSERT Format_size(5_000_000_000_000.0) == "4.54 TBytes",
		   Format_size(5_000_000_000_000.0)
		   
	ASSERT Format_size(6_000_000_000_000_000.0) == "5.32 PBytes",
		   Format_size(6_000_000_000_000_000.0)
		   
	ASSERT Format_size(7_000_000_000_000_000_000.0) == "6.07 HBytes",
		   Format_size(7_000_000_000_000_000_000.0) .



END
*/
