/*

CLASS {File_path|_for_Delphi_KOL},
NATIVE, STOPPING, TESTED(50),
ABSTRACT :

IMPORT: {System_functions}, {Date_time} .
IMPORT: {String_functions} .
//IMPORT, TEST: {Text_file} .

NATIVE: "uses Windows;" .

        FUNCTION Exists|_file(STR Path|_of_file) ==> BOOL, NATIVE :
	        CASE Path == "" ? ==> ;
	        NATIVE     
           @"    Result := TRUE;"
            "    if FileExists(X_Path) then Exit;"
            "    Result := FALSE;" .

        FUN Delete_file(STR Path|_file), NATIVE :
            CASE !Path.Exists ? ==> ;
            Stop_if_test(Path)
            NATIVE @
            "   try"
            "		//if DirectoryExists(X_Path) then"
            "          DeleteFile(PChar(X_Path));" 
            "   except {$IFNDEF NOERR} on E: Exception do {$ENDIF}"
            "       begin"
            "           {$IFNDEF NOERR} Any_exception(E); {$ENDIF}"
            "       end"
            "   end;" .
            
        FUN Can_delete_dir_not_empty ==> BOOL :
            = FALSE .

        FUN Recycle|_file(STR Path|_file), NATIVE :
            CASE !Path.Exists && !Path.Dir_exists ? ==> ;
            Stop_if_test(Path)
            STR list|_paths = Path.Replace_all(";", #0) #0
            NATIVE @
            "	DeleteFile2Recycle(X_list);" .
 
        FUN Remove_file|s_semicolon_separated(
        STR Path|_files), NATIVE :
            Stop_if_test(Path)
            STR list|_paths = Path.Replace_all(";", #0) #0
            NATIVE @
            "	DoFileOp( X_list, '', 3 //FO_DELETE"
		    " 	, 16 //FOF_NOCONFIRMATION"
		    " 	  or 1556 //FOF_NO_UI"
		  	''  , 'Deleting...' );''
            .
            
        FUN Rename|_file(STR Path|_file, STR New|_name), NATIVE :
            CASE !Path.Exists && !Path.Dir_exists ? ==> ;
            Stop_if_test(Path)
            Stop_if_test(New)
            BOOL only_reg|istry_of_letters_changing = Path.Lower == New.Lower
        	NATIVE @
            "	try"
            "    if not X_only_reg and FileExists(X_New) then"
            "		DeleteFile(PChar(X_New));"
            "    if FileExists(X_Path) or DirectoryExists(X_Path) then"
            "       MoveFile(PChar(X_Path), PChar(X_New));"
            "   except {$IFNDEF NOERR} on E: Exception do {$ENDIF}"
            "       begin"
            "           {$IFNDEF NOERR} Any_exception(E); {$ENDIF}"
            "       end"
            "   end;" .

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

        FUN Move_file_direct|ly_without_checks(
        STR Path|_file, STR New|_path), NATIVE : @
        "try"
        "	 MoveFile(PChar(X_Path), PChar(X_New));"
        "except"
        "end;" .

        FUN Copy_file(
        	STR S|ource_file_path, STR D|estination), NATIVE
        	:
            CASE !S.Exists ? ==> ;
            CASE !D.Extract_path.Directory_exists ? ==> ;
            Stop_if_test(S)
            Stop_if_test(D)
            NATIVE : 
            "    CopyFile(PChar(X_S), PChar(X_D), FALSE);" .

        TEST file_manipulations :
             ---------------------------------------------- 'Fake file'
             STR fake|_path = "?"
             CASE !fake.Exists ?
                  fake.Delete_file
                  Rename_file(fake, "??");
             fake = Start_dir.Trailing_slash "My_file.txt"
             CASE fake.Exists ? fake.Delete_file ;
             //text|_file_temp = {Text_file}(Path = fake)
             STR lines|_to_save[] << "line1" << "line2" << "line3"
             CASE !fake.Exists ?
                  //ASSERT text.Save(lines[])
                  lines[0 TO *].Merge(#NL).String_to_file(fake)
                  ASSERT fake.Exists
                  fake.Delete_file
                  ASSERT !fake.Exists
                  ------------------------------------ 'rename'
                  //ASSERT text.Save(lines[])
                  lines[0 TO *].Merge(#NL).String_to_file(fake)
                  ASSERT fake.Exists
                  
                  STR to|_what_rename = Start_dir.Trailing_slash
                      "My_test_file2.txt"
                      
                  CASE to.Exists ?
                       Delete_file(to)
                       ASSERT !to.Exists ;
                  Rename(fake, to)
                  ASSERT to.Exists
                  ------------------------------------ 'move'
                  //ASSERT text.Save(lines[])
                  lines[0 TO *].Merge(#NL).String_to_file(fake)
                  ASSERT fake.Exists
                  
                  to = Start_dir.Trailing_slash
                      "My_test_file2.txt"
                      
                  //STR in|put_test_str
                  //<< "moving to: " to >> in
                  CASE to.Exists ?
                       Delete_file(to)
                       ASSERT !to.Exists ;
                  Move_file(fake, to)
                  ASSERT to.Exists ;
                  ------------------------------------ 'copy'
                  //ASSERT text.Save(lines[])
                  lines[0 TO *].Merge(#NL).String_to_file(fake)
                  ASSERT fake.Exists
                  
                  to = Start_dir.Trailing_slash
                      "My_test_file3.txt"
                      
                  //STR in|put_test_str
                  //<< "copying to: " to >> in
                  CASE to.Exists ?
                       Delete_file(to)
                       ASSERT !to.Exists ;
                  Copy_file(fake, to)
                  ASSERT to.Exists
                  ------------------------------------- 'copy again'
                  CASE (fake "-- not existing").Exists ?
                       Delete_file(fake "-- not existing") ;
                  Copy_file(fake "-- not existing", to)
                  Copy_file(fake, to)
                  Copy_file(fake, to "\#:a\#")
                  ASSERT to.Exists
             .

        FUNCTION Directory_exists || Dir_exists(
        	STR Path|_of_dir) ==> BOOL, NATIVE
        	:
            "    Result := DirectoryExists(X_Path);" .

        FUNCTION Create_dir|ectory(STR Path|_to_new_dir), NATIVE :
           @"    if DirectoryExists(X_Path) then Exit;"
            "    MkDir(X_Path);" .
            
        FUNCTION Change_ext|ension(STR Path|_of_file,
            STR New_ex|tension) ==> STR, NATIVE
            :
            "RESULT := ReplaceExt(X_Path, X_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 *] .            

        TEST changing_extension:
             ASSERT Change_ext("x.xyz", ".abc") == "x.abc"
             ASSERT Change_ext("x/", ".abc") == "x/.abc"
             ASSERT Change_ext("x", ".abc") == "x.abc" .

        FUNCTION Extract_path(
        	STR Path|_of_file_full) ==> STR, NATIVE :
            "RESULT := ExtractFilePath(X_Path);" .

        FUNCTION Extract_file_name(
        	STR Path|_file) ==> STR, NATIVE :
            "RESULT := ExtractFileName(X_Path);" .

        TEST of_extract :
            ASSERT Extract_file_name("path\a") == "a"
            ASSERT Extract_file_name("a") == "a" .

			
        FUN Catalog|ue_of_directory(
        		STR Dir|ectory_path,
                STR Cat|alogue_scanned[]), NATIVE :
           @"var ffd: TFindFileData;"
            "var path: String;"
            "    X_Cat.Clear;"
            "    path := IncludeTrailingPathDelimiter(X_Dir) + '*';"
            "    if Find_First(path, ffd) then"
            "    REPEAT"
            "        if (String(ffd.cFileName) = '.') or "
            "           (ffd.cFileName = '..') then"
            "           continue;"
            "		 if (ffd.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) <> 0"
            "		 then X_Cat.Add(MAXINT, '' + ffd.cFileName + '\')"
            "        else X_Cat.Add(MAXINT, ffd.cFileName);"
            "    UNTIL not Find_Next(ffd);"
            "    Find_Close(ffd);" .
            
        FUN Get_cat|alog|ue|_of_path(
        STR Cat|alogue_scanned[], STR Path|_of_directory)
        :
		Catalog(Path, Cat[]) .
            
        //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',
        //    'RESERVE_1',
        //    'RESERVE_2' .
            
        CONST INT {file_attr|ibutes} : 
	         READ_ONLY		= 1 
	         HIDDEN|_FILE	= 2
	         SYSTEM|_FILE	= 4
	         DIRECTORY		= 16
	         ARCHIVE|_ATTR	= 32
	         NORMAL |_ATTR	= 128
	         TEMPORARY		= 256
	         COMPRESSED		= 0x800
	         OFFLINE|_ATTR	= 0x1000
	         RESERVE_1		= 8
	         RESERVE_2		= 64
	         .
            
        //RECORD {file_info|rmation} :
        //    BOOL Attrib|utes[{file_attr}]
        //    REAL Size|_in_bytes
        //    {date_time} Creation|_date_time
        //    {date_time} Modified|_date_time
        //    {date_time} Accessed|_date_time .

        STRUCTURE {file_detail|ed_info} :
        	//STR  File_name
            //BOOL Attr|ib|utes[{file_attr}]
            INT Attrib|utes
            REAL Size|_in_bytes
            REAL Creation|_date_time
            REAL Modified|_date_time
            REAL 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))
			; .
				
        //FUN Partial_catalog_detailed( STR Dir|ectory_path,
        //    STR Cat|alogue_scanned[], {file_detail} Det|ails_for_files[],
        //    INT Max_items)
        //    :
			//Cat[].Clear
			//Det[].Clear
			//Catalog(Dir, Cat[])
			//STR path|_trailed_slash = Dir.Trailing_slash
			//--------------------- 'getting details on each file found'
			//FOR s IN Cat[0 TO Max_items-1] :
				//Det[] << File_detail(path s.Remove_trailing(Path_separator))
			//; .
				
        FUNCTION File_info(
        	STR Path|_to_file ), ABANDONED('File_detail') 
        	: .

        FUNCTION File_detail|ed_info(
        STR Path|_to_file ) ==> {file_detail}
        	:
			RESULT = {file_detail} //(File_name = Path.Extract_file_name)
            CASE Path == "" ? ==> ;
            INT attr|ibutes[]
            REAL times|_of_file[]
            RESULT.Size = native_File_details(Path, attr[], times[])
            -------------------------------- 'times'
            RESULT.Creation = times[0]
            RESULT.Modified = times[1]
            RESULT.Accessed = times[2]
            -------------------------------- 'attribs'
            RESULT.Attrib = attr[0] .
        FUN native_File_details(STR Path|_of_file,
        	INT attr|ibutes_put_in_0[], REAL times|_of_file[]) ==> REAL,
            NATIVE : @
        @   "var ffd: TFindFileData;"
            "var fa: Integer;"
            "var ST: TSystemTime;"
            "var LT: TFileTime;"
            "var d: TDateTime;"
            "if Find_First(X_Path, ffd) then"
            "begin"
            "    fa := ffd.dwFileAttributes;"
            "	 X_attr.Add(-1, fa);"
            "    Result := ffd.nFileSizeLow + "
            "           ffd.nFileSizeHigh * 4294967296.0;"
            ""
            "    FileTimeToLocalFileTime(ffd.ftCreationTime, LT);"
            "    FileTimeToSystemTime(LT, ST);"
            "	 SystemTime2DateTime(ST, d);"
            "	 X_times.Add(-1, d * 24.0 * 3600000);"
            ""
            "    FileTimeToLocalFileTime(ffd.ftLastWriteTime, LT);"
            "    FileTimeToSystemTime(LT, ST);"
            "	 SystemTime2DateTime(ST, d);"
            "	 X_times.Add(-1, d * 24.0 * 3600000);"
            ""
            "    FileTimeToLocalFileTime(ffd.ftLastAccessTime, LT);"
            "    FileTimeToSystemTime(LT, ST);"
            "	 SystemTime2DateTime(ST, d);"
            "	 X_times.Add(-1, d * 24.0 * 3600000);"
            ""
            "    Find_Close(ffd);"
            "end;"
            .
            
        FUN File_size_kbytes(STR File_name) ==> REAL :
        	RESULT = File_detail(File_name).Size / 1024 .

            
		FUNCTION Get_file_time_age_seconds(
		STR Path|_file) ==> REAL, 
		DEPRECATED('use only for compatibility with the primary AL4 compiler')
			:
			{file_detail} f|details_temp = File_detail(Path)
			RESULT = Now.Seconds_diff(Date_time(DT = f.Modified)) .
			
        FUNCTION Get_file_modified_time_formatted(
		STR Path|_file) ==> STR, 
		DEPRECATED('use only for compatibility with the primary AL4 compiler')
		:
		CASE Path == "" || (!Path.Exists && !Path.Dir_exists) ? ==> ;
		{file_detail} f|details_temp = File_detail(Path)
		RESULT = Date_time(DT = f.Modified).Format("yyyyMMdd HHmmss") .

		FUN Set_file_time_age(
		STR Path|_file, {Date_time}.{date_time} Age|_2_seconds_recision),
		DEPRECATED('Platform dependant')
			:
			do_Set_file_time_age(Path, Age.DT) .
	 	FUN do_Set_file_time_age(
	 	STR Path|_file, REAL Age|_2_seconds_recision), NATIVE :
			CASE !Path.Exists ? ==> ;
		NATIVE @
			"var ft: TFileTime; st: TSystemTime; hFile: THandle;"
			"hFile := CreateFile(PChar(X_Path), 256{FILE_WRITE_ATTRIBUTES}, "
			"	FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);"
			"if hFile = INVALID_HANDLE_VALUE then Exit;"
			"DateTime2SystemTime(X_Age/24.0/3600000,st);"
			"SystemTimeToFileTime(st, ft);"
			"SetFileTime(hFile, nil, nil, @ft);" //<fixed params order 31.08.2021>
			"CloseHandle(hFile);" 
			.
			
		FUN Set_file_creation_time(
		STR Path|_file, {Date_time}.{date_time} CT|ime_2_seconds_recision),
		DEPRECATED('Platform dependant')
			:
			do_Set_file_creation_time(Path, CT.DT) .
	 	FUN do_Set_file_creation_time(
	 	STR Path|_file, REAL CT|_2_seconds_recision), NATIVE,
		DEPRECATED('Platform dependant') :
			CASE !Path.Exists ? ==> ;
		NATIVE @
			"var ft: TFileTime; st: TSystemTime; hFile: THandle;"
			"hFile := CreateFile(PChar(X_Path), 256{FILE_WRITE_ATTRIBUTES}, "
			"	FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);"
			"if hFile = INVALID_HANDLE_VALUE then Exit;"
			"DateTime2SystemTime(X_CT/24.0/3600000,st);"
			"SystemTimeToFileTime(st, ft);"
			"SetFileTime(hFile, @ft, nil, nil);"
			"CloseHandle(hFile);" 
			.
			
		FUN Set_file_last_access_time(
		STR Path|_file, {Date_time}.{date_time} CT|ime_2_seconds_recision),
		DEPRECATED('Platform dependant')
			:
			do_Set_file_last_access_time(Path, CT.DT) .
	 	FUN do_Set_file_last_access_time(
	 	STR Path|_file, REAL CT|_2_seconds_recision), NATIVE,
		DEPRECATED('Platform dependant') :
			CASE !Path.Exists ? ==> ;
		NATIVE @
			"var ft: TFileTime; st: TSystemTime; hFile: THandle;"
			"hFile := CreateFile(PChar(X_Path), 256{FILE_WRITE_ATTRIBUTES}, "
			"	FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);"
			"if hFile = INVALID_HANDLE_VALUE then Exit;"
			"DateTime2SystemTime(X_CT/24.0/3600000,st);"
			"SystemTimeToFileTime(st, ft);"
			"SetFileTime(hFile, nil, nil, @ft);"
			"CloseHandle(hFile);" 
			.
			
		FUN Set_file_attr|ibutes(
		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 @
		"var flags: DWORD;"
		"	flags := FILE_ATTRIBUTE_NORMAL;"
		"	if (X_hidden) then flags := flags or FILE_ATTRIBUTE_HIDDEN;"
		"	if (X_readonly) then flags := flags or FILE_ATTRIBUTE_READONLY;"
		"	if (X_system) then flags := flags or FILE_ATTRIBUTE_SYSTEM;"
		"	SetFileAttributes(PChar(X_Path), flags);" .
			
			
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) .
		   
FUN File_to_string | for_small_files_ONLY(
STR Path|_of_file) ==> STR, NATIVE : @
	"var f: THandle;"
	"var Hi: DWORD;"
	"	f := FileCreate(X_Path, ofOpenRead or ofOpenExisting or ofShareDenyNone);"
	"	try"
	"		if (GetFileSize(f, @Hi) > 1000000) or (Hi <> 0) then Exit;"
	"		RESULT := File2Str(f);"
	"	finally"
	"		FileClose(f);" 
	"	end;" .
		   
FUN Oem_file_to_string | for_small_files_ONLY(
STR Path|_of_file) ==> STR
	:= File_to_string(Path) .

FUN String_to_file(
STR S|tring_to_save, STR Path|_of_file), NATIVE :
	Stop_if_test(Path)
	NATIVE @
	"var f: THandle;"
	"	f := FileCreate(X_Path, ofOpenWrite or ofOpenAlways);"
	"	try"
	"		FileWrite(f, X_S[1], Length(X_S));"
	"	finally"
	"		FileClose(f);" 
	"	end;" .

		   
		   
END

