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



//
// PATH_SEPARATOR, PATH_LIST_SEPARATOR, rootDirectory?, absolutePath?
//

alias PATH_SEPARATOR = '/';
alias PATH_LIST_SEPARATOR = ':';

rootDirectory?(s) = (s == "/");
absolutePath?(s) = beginsWith?(s, '/');



//
// slicedPath, splitPath, pathComponents
//

slicedPath(path) {
    var path2 = directoryTailTrimmed(path);
    var p = reverseFind(path2, '/');
    var parent, child = slicedUpto(path2, p), slicedFrom(path2, p);
    return directoryTailTrimmed(parent), child;
}

pathDirname(path) = String(nthValue(static 0, ..slicedPath(path)));
pathBasename(path) = String(nthValue(static 1, ..slicedPath(path)));

splitPath(path) = ..mapValues(force, ..slicedPath(path));

private directoryTailTrimmed(path) {
    if (empty?(path))
        return slicedUpto(path, 0);
    var p = reverseFindIf(begin(path)+1, end(path), c => (c != '/'));
    return slicedUpto(path, p);
}

pathComponents(path) {
    var parts = Vector[String]();
    var parent, child = ..slicedPath(path);
    while (not empty?(child)) {
        push(parts, String(child));
        parent, child = ..slicedPath(parent);
    }
    if (not empty?(parent))
        push(parts, String(parent));
    reverseInPlace(parts);
    return move(parts);
}



//
// joinPath
//

define joinPath;

[..S | allValues?(String?, ..S)]
overload joinPath(..parts:S) {
    var result = String();
    ..for (part in parts)
        appendPath(result, part);
    trimDirectoryTail(result);
    return move(result);
}

[A | String?(SequenceElementType(A))]
overload joinPath(parts:A) {
    var result = String();
    for (part in parts)
        appendPath(result, part);
    trimDirectoryTail(result);
    return move(result);
}

private appendPath(path:String, part) {
    if (absolutePath?(part)) {
        path = String(part);
    }
    else {
        if ((not empty?(path)) and (not endsWith?(path, '/')))
            push(path, '/');
        push(path, part);
    }
}

private trimDirectoryTail(path:String) {
    if (endsWith?(path, '/') and (not rootDirectory?(path)))
        pop(path);
}



//
// pathExists?
//

pathExists?(p) {
    var statBuf = unix.Struct_stat();
    var result = unix.lstat(cstring(p), &statBuf);
    return result == 0;
}



//
// systemPath, currentDirectory
//

systemPath() {
    var pathList = CStringRef(libc.getenv(cstring("PATH")));
    return split(pathList, PATH_LIST_SEPARATOR);
}

currentDirectory() {
    var buf = String();
    resize(buf, 1024);

    while (true) {
        var result = unix.getcwd(Pointer[CChar](begin(buf)), size(buf));
        if (not null?(result))
            break;
        var err = unix.errno();
        if (err != unix.ERANGE)
            throw GenericIOError(err);
        resize(buf, 2*size(buf));
    }
 
    resize(buf, libc.strlen(Pointer[CChar](begin(buf))));
    return move(buf);
}



//
// pathToExecutable
//

define pathToExecutable;

overload pathToExecutable() = pathToExecutable(commandLine()[0]);

overload pathToExecutable(arg0) {
    var searchDirs = systemPath();
    insert(searchDirs, 0, currentDirectory());
    for (pathDir in searchDirs) {
        var p = joinPath(pathDir, arg0);
        if (pathExists?(p))
            return move(p);
    }
    return String();
}
