/*
CLASS {File_path|_for_Android}, NATIVE, UNTESTED, STOPPING :

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

NATIVE: @   "import java.nio.file.Files;"
            "import java.nio.file.attribute.*;" 
            "import java.io.*;" .

        
        FUNCTION Exists|_file(STR Path|_of_file) ==> BOOL, NATIVE :
	        CASE Path == "" ? ==> ;
	        NATIVE     
           @"    if (new java.io.File(X_Path).isFile()) RESULT = true;"
            "    else RESULT = false;" .
        */
        public static bool Exists(string Path) {
            if (File.Exists(Path)) return true;
            try {
                string[] ff = Directory.GetFiles(System.IO.Path.GetDirectoryName(Path));
                Path = Path.ToLower();
                for (int i = ff.Length - 1; i >= 0; i--) {
                    string s = ff[i].ToLower();
                    if (s == Path) return true;
                }
            }
            catch(Exception E) { 
            }
            return false;
        }

        /*
        
        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 {"
            "        new java.io.File(X_Path).delete();"
            "    }"
            "    catch (Exception E) {"
            "    }" .
            
        FUN Recycle_file(STR Path|_file_dummy), UNCOOKED : .
        
        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"
            ;
            rename(Path, New) .

        FUN rename|_file(STR Path|_file, STR New|_name), NATIVE :
           @"    try {"
            "        java.io.File old = new java.io.File(X_Path);"
            "        if (!old.isFile()) return;"
            "        java.io.File dst = new java.io.File(X_New);"
            "        if (dst.isFile())"
            "           dst.delete();"
            "        if (old.isFile())"
            "           old.renameTo(dst);"
            "    }"
            "    catch(Exception E) {"
            "    }" .
        
        FUN Move_file || Move_file_direct(
        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)
            do_copy_file(S, D)
            {file_detail} fa|ttributes_source = File_detail(S)
            Set_file_time_age(D, Date_time(DT= fa.Modified)) .

        FUN do_copy_file(STR S|ource_file_path, STR D|estination),
                NATIVE :
           @"    try {"
            "        java.io.InputStream src = null;"
            "        java.io.OutputStream dst = null;"
            "        try {"
            "            new java.io.File(X_D).delete();"
            "            src = new java.io.FileInputStream(X_S);"
            "            dst = new java.io.FileOutputStream(X_D);"
            "            byte[] buf = new byte[65536];"
            "            int bytesRead;"
            "            while ((bytesRead = src.read(buf)) > 0) {"
            "                  dst.write(buf, 0, bytesRead);"
            "            }"
            "        }"
            "        finally {"
            "                src.close();"
            "                dst.close();"
	    "        }"
            "    }"
            "    catch (java.io.IOException E) {"
            "    }".

        
        FUNCTION Directory_exists || Dir_exists(
        STR Path|_of_dir) ==> BOOL, NATIVE :
            "    java.io.File f = new java.io.File(X_Path);"
            "    RESULT = f.exists() && f.isDirectory();" .
        
        FUNCTION Create_dir|ectory(STR Path|_to_new_dir), NATIVE :
            "    new java.io.File(X_Path).mkdirs();" .
        */
        public static void Create_dir(string path)
        {
            Directory.CreateDirectory(path);
        }

        /*
        
        FUN Catalog|ue_of_directory(
        STR Dir|ectory_path, STR Cat|alogue_scanned[]), NATIVE :
           @"    java.io.File[] files = new java.io.File(X_Dir).listFiles();"
            "    X_Cat.Clear();"
            "    for (java.io.File file : files)"
            "        X_Cat.Add(file.getName());" .

        FUN Get_cat|alog|ue|_of_path(
        STR Cat|alogue_scanned[], STR Path|_of_directory)
        :
		Catalog(Path, Cat[]) .
		
        */
        public static void Catalog(string dir, List<string> cat)
        {
            string[] dirs = Directory.GetDirectories(dir);
            string[] files = Directory.GetFiles(dir, "*.*", SearchOption.TopDirectoryOnly);
            foreach (string d in dirs)
                    cat.Add(Path.GetFileName(d) + "\\");
            foreach (string f in files)
                    cat.Add(Path.GetFileName(f));
        }
        
        /*

        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' .
            
        STRUCTURE {file_detail|ed_info} :
            BOOL Attr|ib|utes[{file_attr}]
            REAL Size|_in_bytes
            REAL Creation|_date_time
            REAL Modified|_date_time
            REAL Accessed|_date_time .
            
        FUN Detailed_catalog|ue_of_directory(
        STR Dir|ectory_path,
        STR Cat|alogue_scanned[]//, {file_info} Det|ails_array[]
        ), ABANDONED('Catalog_detailed') : .
            
        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_info(
        STR File_path ), 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 == "" ? ==> ;
            STR 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['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 :
        @   "    java.nio.file.Path p = "
            "    java.nio.file.FileSystems.getDefault().getPath(X_Path);"
            "    DosFileAttributes da = Files.readAttributes("
            "       p, DosFileAttributes.class);"
            "	 String a = "#QU#QU";"
            "    if (da.isReadOnly()) a += 'R';"
            "    if (da.isHidden()) a += 'H';"
            "    if (da.isSystem()) a += 'S';"
            "    if (da.isArchive()) a += 'A';"
            "    BasicFileAttributes ba = Files.readAttributes("
            "       p, BasicFileAttributes.class);"
            "    if (ba.isDirectory()) a += 'D';"
            "	 X_attr.Add(a);"
            "    RESULT = ba.size();"
            "    X_times.Add(ba.creationTime().toMillis());"
            "    X_times.Add(ba.lastModifiedTime().toMillis());"
            "    X_times.Add(ba.lastAccessTime().toMillis());" .
        
        FUNCTION Change_ext|ension(STR Path|_of_file,
            STR New_ex|tension) ==> STR
            :
            ---------------------------- 'Search dot or slash from end to start'
            FOR i IN [Path.Len-1 DOWNTO 0] :
                CASE Path.[i] ?
                    ["."]: RESULT = Path.[0 TO i-1] New_ex ==>
                    ["\", "/"]: BREAK i ;
            ;
            ---------------------------- 'If not found, just append'
            RESULT = Path New_ex .

        
        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 Ext|enstion(STR Path|_of_file) ==> STR :
			CASE "." IN Path ?
				 RESULT = Path.[Path.Find_last(".") TO *] ; .
            
        FUNCTION Fix_path(
        STR Path|_if_dir_not_specified_then_add_prefix_workdir) ==> STR
        	:
        	CASE "/" IN Path || "\" IN Path ? RESULT = Path
        	ELSE RESULT = Get_working_dir Path ; .
        	
		FUN Set_file_attr|ibutes(
		STR Path|_file, STR Attr|ibutes_Hidden_Readonly_System),
		UNCOOKED : .
			
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 Set_file_time_age(
STR Path|_file, {Date_time}.{date_time} Age|_2_seconds_recision),
DEPRECATED('Platform dependant'), UNCOOKED
	:
	.
			
FUN Set_file_creation_time(
STR Path|_file, {Date_time}.{date_time} CT|ime_creation),
DEPRECATED('Platform dependant'), UNCOOKED
	:
	.
			
FUN Set_file_last_access_time(
STR Path|_file, {Date_time}.{date_time} CT|ime_creation),
DEPRECATED('Platform dependant'), UNCOOKED
	:
	.
			
FUN File_to_string(STR Path|_of_file) ==> STR, NATIVE :
	CASE !Path.Exists ? ==> ;
	NATIVE @
	"	File f = new File(X_Path);"
    "   if (f.isFile()) {"
    "     	RandomAccessFile fs = new RandomAccessFile(f, "#QU"r"#QU");"
	"	    try {"
	"		     long len = fs.length();"
	"		     if (len > 0x100000) len = 0x100000;"
    "            byte[] read_buf = new byte[(int)len];"
    "            fs.read(read_buf, 0, (int)len);"
	"        	 RESULT = new String(read_buf, " #QU "ASCII" #QU ");"
	"	    }"
	"	    finally {"
	"		    fs.close();" 
    "	    }"
	"	}" .
		   
FUN Oem_file_to_string(STR Path|_of_file) ==> STR 
	:= File_to_string(Path) .
		   
		   
END
*/
