import unix.*;
import io.errors.*;



//
// directoryListing
//

directoryListing(x) =
    filtered(notCurrentOrPrevious?, mapped(direntName, direntSequence(x)));

private notCurrentOrPrevious?(x) = not (x == "." or x == "..");
private direntName(x) = CStringRef(begin(x.d_name));



//
// DirentSequence
//

record DirentSequence (
    dir : Pointer[DIR],
);

overload RegularRecord?(static DirentSequence) = false;
overload BitwiseMovedType?(static DirentSequence) = true;

[T | CCompatibleString?(T)]
overload DirentSequence(dir:T) --> returned: DirentSequence {
    returned.dir = opendir(cstring(dir));
    if (null?(returned.dir))
        throw GenericIOError(errno());
}

overload destroy(s: DirentSequence) {
    if (not null?(s.dir)) {
        var result = closedir(s.dir);
        if (result == Type(result)(-1))
            errorNoThrow(errorMessage(errno()));
    }
}
overload resetUnsafe(s: DirentSequence) {
    s.dir = null(DIR);
}

[T | CCompatibleString?(T)]
direntSequence(dir:T) = DirentSequence(dir);

overload iterator(s:DirentSequence) = DirentIterator(s.dir, Struct_dirent());



//
// DirentIterator
//

private record DirentIterator (
    dir : Pointer[DIR],
    entry : Struct_dirent,
);

overload hasNext?(x:DirentIterator) {
    var ptr = Pointer[Struct_dirent]();
    var returnCode = readdir_r(x.dir, &x.entry, &ptr);
    if (returnCode != 0)
        throw GenericIOError(errno());
    return not null?(ptr);
}

overload next(x:DirentIterator) {
    return x.entry;
}
