# get and set current directory

function pwd()
    b = Array(Uint8,1024)
    @unix_only p = ccall(:getcwd, Ptr{Uint8}, (Ptr{Uint8}, Csize_t), b, length(b))
    @windows_only p = ccall(:_getcwd, Ptr{Uint8}, (Ptr{Uint8}, Cint), b, length(b))
    systemerror(:getcwd, p == C_NULL)
    bytestring(p)
end

function cd(dir::String) 
    @windows_only systemerror("chdir $dir", ccall(:_chdir,Int32,(Ptr{Uint8},),dir) == -1)
    @unix_only systemerror("chdir $dir", ccall(:chdir,Int32,(Ptr{Uint8},),dir) == -1)
end
cd() = cd(homedir())

@unix_only function cd(f::Function, dir::String, args...)
    fd = ccall(:open,Int32,(Ptr{Uint8},Int32),".",0)
    systemerror(:open, fd == -1)
    try
        cd(dir)
        f(args...)
    finally
        systemerror(:fchdir, ccall(:fchdir,Int32,(Int32,),fd) != 0)
        systemerror(:close, ccall(:close,Int32,(Int32,),fd) != 0)
    end
end
@windows_only function cd(f::Function, dir::String, args...)
    old = pwd()
    try
        cd(dir)
        f(args...)
   finally
        cd(old)
    end
end
cd(f::Function) = cd(f, homedir())

function mkdir(path::String, mode::Unsigned=0o777)
    @unix_only ret = ccall(:mkdir, Int32, (Ptr{Uint8},Uint32), bytestring(path), mode)
    @windows_only ret = ccall(:_mkdir, Int32, (Ptr{Uint8},), bytestring(path))
    systemerror(:mkdir, ret != 0)
end

function mkpath(path::String, mode::Unsigned=0o777)
    isdirpath(path) && (path = dirname(path))
    dir = dirname(path)
    (path == dir || isdir(path)) && return
    mkpath(dir, mode)
    mkdir(path)
end

mkdir(path::String, mode::Signed) = error("mkdir: mode must be an unsigned integer -- perhaps 0o$mode?")
mkpath(path::String, mode::Signed) = error("mkpath: mode must be an unsigned integer -- perhaps 0o$mode?")

function rmdir(path::String)
    @unix_only ret = ccall(:rmdir, Int32, (Ptr{Uint8},), bytestring(path))
    @windows_only ret = ccall(:_rmdir, Int32, (Ptr{Uint8},), bytestring(path))
    systemerror(:rmdir, ret != 0)
end

# The following use Unix command line facilites

rm(path::String) = FS.unlink(path)
cp(src::String, dst::String) = run(`cp $src $dst`)
mv(src::String, dst::String) = run(`mv $src $dst`)
touch(path::String) = run(`touch $path`)

# Obtain a temporary filename.
function tempname()
    d = get(ENV, "TMPDIR", C_NULL) # tempnam ignores TMPDIR on darwin
    @unix_only p = ccall(:tempnam, Ptr{Uint8}, (Ptr{Uint8},Ptr{Uint8}), d, "julia")
    @windows_only p = ccall(:_tempnam, Ptr{Uint8}, (Ptr{Uint8},Ptr{Uint8}), d, "julia")
    s = bytestring(p)
    c_free(p)
    s
end

# Obtain a temporary directory's path.
tempdir() = dirname(tempname())

# Create and return the name of a temporary file along with an IOStream
@unix_only function mktemp()
    b = joinpath(tempdir(), "tmpXXXXXX")
    p = ccall(:mkstemp, Int32, (Ptr{Uint8}, ), b)
    return (b, fdio(p, true))
end

@windows_only begin 
function GetTempPath()
    temppath = Array(Uint8,261)
    lentemppath = ccall(:GetTempPathA,stdcall,Uint32,(Uint32,Ptr{Uint8}),length(temppath),temppath)
    if lentemppath >= length(temppath) || lentemppath == 0
        error("GetTempPath failed")
    end
    resize!(temppath,lentemppath)
    return convert(ASCIIString,temppath)
end
GetTempFileName(uunique::Uint32) = GetTempFileName(GetTempPath(), uunique)
function GetTempFileName(temppath::String,uunique::Uint32)
    tname = Array(Uint8,261)
    uunique = ccall(:GetTempFileNameA,stdcall,Uint32,(Ptr{Uint8},Ptr{Uint8},Uint32,Ptr{Uint8}),temppath,"julia",uunique,tname)
    lentname = findfirst(tname,0)-1
    if uunique == 0 || lentname <= 0
        error("GetTempFileName failed")
    end
    resize!(tname,lentname)
    return convert(ASCIIString, tname)
end
function mktemp()
    filename = GetTempFileName(uint32(0))
    return (filename, open(filename,"r+"))
end
end

# Create and return the name of a temporary directory
@unix_only function mktempdir()
    b = joinpath(tempdir(), "tmpXXXXXX")
    p = ccall(:mkdtemp, Ptr{Uint8}, (Ptr{Uint8}, ), b)
    return bytestring(p)
end

@windows_only function mktempdir()
    seed = rand(Uint32)
    while true
        filename = GetTempFileName(seed)
        ret = ccall(:_mkdir, Int32, (Ptr{Uint8},), filename)
        if ret == 0
            return filename
        end
        systemerror(:mktempdir, errno()!=EEXIST)
        seed += 1
    end
end

downloadcmd = nothing
function download(url::String, filename::String)
    global downloadcmd
    if downloadcmd === nothing
        for checkcmd in (:curl, :wget, :fetch)
            if success(`which $checkcmd` |> DevNull)
                downloadcmd = checkcmd
                break
            end
        end
    end
    if downloadcmd == :wget
        run(`wget -O $filename $url`)
    elseif downloadcmd == :curl
        run(`curl -o $filename -L $url`)
    elseif downloadcmd == :fetch
        run(`fetch -f $filename $url`)
    else
        error("No download agent available; install curl, wget, or fetch.")
    end
    filename
end
function download(url::String)
    filename = tempname()
    download(url, filename)
end

function readdir(path::String)
    # Allocate space for uv_fs_t struct
    uv_readdir_req = zeros(Uint8, ccall(:jl_sizeof_uv_fs_t, Int32, ()))

    # defined in sys.c, to call uv_fs_readdir
    file_count = ccall(:jl_readdir, Int32, (Ptr{Uint8}, Ptr{Uint8}),
                       bytestring(path), uv_readdir_req)

    if file_count < 0
        error("Unable to read directory $path.")
    end

    # The list of dir entries is returned as a contiguous sequence of null-terminated
    # strings, the first of which is pointed to by ptr in uv_readdir_req.
    # The following lines extracts those strings into dirent
    entries = String[]
    offset = 0

    for i = 1:file_count
        entry = bytestring(ccall(:jl_uv_fs_t_ptr_offset, Ptr{Uint8},
                                 (Ptr{Uint8}, Int32), uv_readdir_req, offset))
        push!(entries, entry)
        offset += sizeof(entry) + 1   # offset to the next entry
    end

    # Clean up the request string
    ccall(:jl_uv_fs_req_cleanup, Void, (Ptr{Uint8},), uv_readdir_req)

    entries
end

readdir() = readdir(".")
