import win32;
import io.errors.*;


//
// RawFile
//

record RawFile (
    handle : win32.HANDLE,
    owned : Bool,
);

overload RegularRecord?(static RawFile) = false;

private stdHandle(x) = win32.GetStdHandle(win32.DWORD(x));

stdinRawFile()  = RawFile(stdHandle(win32.STD_INPUT_HANDLE), false);
stdoutRawFile() = RawFile(stdHandle(win32.STD_OUTPUT_HANDLE), false);
stderrRawFile() = RawFile(stdHandle(win32.STD_ERROR_HANDLE), false);



//
// constructors, destroy
//

private accessFlags(mode) {
    switch (mode)
    case (READ)
        return win32.GENERIC_READ;
    case (READ_WRITE)
        return bitor(win32.GENERIC_READ, win32.GENERIC_WRITE);
    case (CREATE)
        return bitor(win32.GENERIC_READ, win32.GENERIC_WRITE);
    case (APPEND)
        return win32.GENERIC_WRITE;
    else {
        assert(false);
        return 0u;
    }
}

private sharingFlags(mode) {
    switch (mode)
    case (READ)
        return win32.FILE_SHARE_READ;
    case (READ_WRITE)
        return 0u;
    case (CREATE)
        return 0u;
    case (APPEND)
        return 0u;
    else {
        assert(false);
        return 0u;
    }
}

private creationFlags(mode) {
    switch (mode)
    case (READ)
        return win32.OPEN_EXISTING;
    case (READ_WRITE)
        return win32.OPEN_EXISTING;
    case (CREATE)
        return win32.CREATE_ALWAYS;
    case (APPEND)
        return win32.OPEN_ALWAYS;
    else {
        assert(false);
        return 0u;
    }
}

[S | CCompatibleString?(S)]
overload RawFile(path:S, mode:OpenMode) {
    var handle = win32.CreateFileA(
        cstring(path),
        win32.DWORD(accessFlags(mode)),
        win32.DWORD(sharingFlags(mode)),
        win32.LPSECURITY_ATTRIBUTES(0),
        win32.DWORD(creationFlags(mode)),
        win32.DWORD(win32.FILE_ATTRIBUTE_NORMAL),
        win32.HANDLE(0));
    if (handle == win32.INVALID_HANDLE_VALUE)
        throw GenericIOError(win32.GetLastError());
    var f = RawFile(handle, true);
    if (mode == APPEND)
        seek(f, 0, SEEK_END);
    return move(f);
}

[S | CCompatibleString?(S)]
overload RawFile(path:S) = RawFile(path, READ);

overload destroy(x:RawFile) {
    if (x.owned) {
        var result = win32.CloseHandle(x.handle);
        if (result == 0)
            errorNoThrow(GenericIOError(win32.GetLastError()));
    }
}


// moveUnsafe, resetUnsafe

overload BitwiseMovedType?(static RawFile) = true;

overload resetUnsafe(x:RawFile) {
    x <-- RawFile(win32.HANDLE(0), false);
}




//
// fileHandle
//

overload fileHandle(f:RawFile) = f.handle;



//
// read, write, flush
//

overload read(f:RawFile, buffer:Pointer[Byte], n:SizeT) : SizeT {
    var nRead = win32.DWORD();
    var result = win32.ReadFile(
        f.handle,
        win32.LPVOID(buffer),
        win32.DWORD(n),
        &nRead,
        win32.LPOVERLAPPED(0));
    if (result == 0)
        throw GenericIOError(win32.GetLastError());
    return SizeT(nRead);
}

overload write(f:RawFile, buffer:Pointer[Byte], n:SizeT) {
    var nWritten = win32.DWORD();
    var result = win32.WriteFile(
        f.handle,
        win32.LPVOID(buffer),
        win32.DWORD(n),
        &nWritten,
        win32.LPOVERLAPPED(0));
    if (result == 0)
        throw GenericIOError(win32.GetLastError());
    assert(nWritten == n);
}

overload flush(f:RawFile) {
    win32.FlushFileBuffers(f.handle);
    // Don't check for errors because
    // FlushFileBuffers can legitimately fail for unbuffered
    // hanldes such as console handles.
}



//
// fileSize, seek
//

overload fileSize(f:RawFile) {
    var size = Int64();
    var result = win32.GetFileSizeEx(f.handle, &size);
    if (result == 0)
        throw GenericIOError(win32.GetLastError());
    return size;
}

private seekMethod(x) {
    switch (x)
    case (SEEK_SET) return win32.FILE_BEGIN;
    case (SEEK_CUR) return win32.FILE_CURRENT;
    case (SEEK_END) return win32.FILE_END;
    else {
        assert(false);
        return 0u;
    }
}

overload seek(f:RawFile, offset, whence) {
    var newPos = Int64();
    var result = win32.SetFilePointerEx(
        f.handle,
        Int64(offset),
        &newPos,
        seekMethod(whence));
    if (result == 0)
        throw GenericIOError(win32.GetLastError());
    return newPos;
}



//
// terminal?
//

overload terminal?(f:RawFile) = true; // FIXME: implement it
