import win32;
import io.errors.*;


//
// FileMapping
//

private record InnerFileMapping (
    fileHandle : win32.HANDLE,
    mappingObject : win32.HANDLE,
    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 win32.PAGE_READONLY;
    case (MAP_READ_WRITE)
        return win32.PAGE_READWRITE;
    case (MAP_COPY_ON_WRITE)
        return win32.PAGE_WRITECOPY;
    else {
        assert(false);
        return 0u;
    }
}

private modeAccess(mode) {
    switch (mode)
    case (MAP_READ)
        return win32.FILE_MAP_READ;
    case (MAP_READ_WRITE)
        return win32.FILE_MAP_WRITE;
    case (MAP_COPY_ON_WRITE)
        return win32.FILE_MAP_COPY;
    else {
        assert(false);
        return 0u;
    }
}

[A,B | Integer?(A,B)]
overload FileMapping(f:File, mode:MappingMode, offset:A, length:B) {
    var mappingObject = win32.CreateFileMappingA(
        fileHandle(f),
        win32.LPSECURITY_ATTRIBUTES(0),
        win32.DWORD(modeProtection(mode)),
        win32.DWORD(0),
        win32.DWORD(0),
        win32.LPCSTR(0));
    if (mappingObject == win32.HANDLE(0))
        throw GenericIOError(win32.GetLastError());
    var offset64 = UInt64(offset);
    var offsetHigh = bitshr(offset64, 32);
    var offsetLow = bitand(offset64, bitshl(1ul,32)-1);
    var address = win32.MapViewOfFile(
        mappingObject,
        win32.DWORD(modeAccess(mode)),
        win32.DWORD(offsetHigh),
        win32.DWORD(offsetLow),
        win32.SIZE_T(length));
    if (address == win32.LPVOID(0)) {
        win32.CloseHandle(mappingObject);
        throw GenericIOError(win32.GetLastError());
    }
    var p = new(InnerFileMapping(fileHandle(f), mappingObject,
                                 address, SizeT(length)));
    return FileMapping(p);
}

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

overload destroy(x:InnerFileMapping) {
    var result = win32.UnmapViewOfFile(x.address);
    if (result == 0)
        errorNoThrow(GenericIOError(win32.GetLastError()));
    result = win32.CloseHandle(x.mappingObject);
    if (result == 0)
        errorNoThrow(GenericIOError(win32.GetLastError()));
}



//
// mappingAddress, mappingLength
//

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



//
// flushMapping
//

flushMapping(fm:FileMapping) {
    var result = win32.FlushViewOfFile(fm.inner^.address, fm.inner^.length);
    if (result == 0)
        throw GenericIOError(win32.GetLastError());
    result = win32.FlushFileBuffers(fm.inner^.fileHandle);
    if (result == 0)
        throw GenericIOError(win32.GetLastError());
}
