import unix;
import io.errors.*;


//
// FileMapping
//

private record InnerFileMapping (
    address : Pointer[Byte],
    length : SizeT,
);

overload RegularRecord?(static InnerFileMapping) = false;

record FileMapping (
    inner : SharedPointer[InnerFileMapping],
);



//
// constructors, destroy
//

enum MappingMode (
    MAP_READ,
    MAP_READ_WRITE,
    MAP_COPY_ON_WRITE
);

private modeProtection(mode) {
    switch (mode)
    case (MAP_READ)
        return unix.PROT_READ;
    case (MAP_READ_WRITE)
        return bitor(unix.PROT_READ, unix.PROT_WRITE);
    case (MAP_COPY_ON_WRITE)
        return bitor(unix.PROT_READ, unix.PROT_WRITE);
    else {
        assert(false);
        return -1;
    }
}

private modeFlags(mode) {
    switch (mode)
    case (MAP_READ)          return unix.MAP_SHARED;
    case (MAP_READ_WRITE)    return unix.MAP_SHARED;
    case (MAP_COPY_ON_WRITE) return unix.MAP_PRIVATE;
    else {
        assert(false);
        return -1;
    }
}

[A,B | Integer?(A,B)]
overload FileMapping(f:File, mode:MappingMode, offset:A, length:B) {
    var protection = modeProtection(mode);
    var flags = modeFlags(mode);
    var address = unix.mmap(
        RawPointer(0),
        SizeT(length),
        Int(protection),
        Int(flags),
        fileHandle(f),
        Int64(offset),
    );
    if (address == Type(address)(-1))
        throw GenericIOError(unix.errno());
    return FileMapping(new(InnerFileMapping(address, SizeT(length))));
}

overload FileMapping(f:File, mode:MappingMode) =
    FileMapping(f, mode, 0, fileSize(f));

overload destroy(x:InnerFileMapping) {
    var result = unix.munmap(x.address, x.length);
    if (result == Type(result)(-1))
        errorNoThrow(GenericIOError(unix.errno()));
}



//
// mappingAddress, mappingLength
//

mappingAddress(fm:FileMapping) = fm.inner^.address;
mappingLength(fm:FileMapping) = fm.inner^.length;



//
// flushMapping
//

flushMapping(fm:FileMapping) {
    var result = unix.msync(fm.inner^.address, fm.inner^.length, unix.MS_SYNC);
    if (result == Type(result)(-1))
        throw GenericIOError(unix.errno());
}
