module std::io::file::mmap @if(env::LIBC &&& env::POSIX);
import std::core::mem::vm, std::io::file;

struct FileMmap
{
	File file;
	VirtualMemory vm;
	usz offset;
	usz len;
}

<*
 Provides a slice of bytes to the expected mapped range discarding the extra bytes due to misaligment of offset and/or size.

 @return "Slice of the mapped range where the first byte matches the file's byte at the offset specified to File::file_mmap()"
*>
fn char[] FileMmap.bytes(&self)
{
	return self.vm.ptr[self.offset:self.len];
}

<*
  Destroys the underlyng VirtualMemory object ie. calls munmap()"
*>
fn void? FileMmap.destroy(&self) @maydiscard
{
	fault err1 = @catch(self.file.close());
	fault err2 = @catch(self.vm.destroy());
	if (err1) return err1?;
	if (err2) return err2?;
}

module std::io::file @if(env::LIBC &&& env::POSIX);

<*
 Maps a region of an already-opened file into memory

 @param file : "Already opened file created on the caller scope"
 @param offset : "Byte offset in file, will be rounded down to page size"
 @param len : "Size in bytes to map starting from offset, will be rounded up to page size"
 @return? mem::OUT_OF_MEMORY, vm::ACCESS_DENIED, vm::RANGE_OVERFLOW, vm::INVALID_ARGS, vm::UNKNOWN_ERROR, io::NO_PERMISSION, io::FILE_NOT_VALID, io::WOULD_BLOCK, io::FILE_NOT_FOUND
 @return "Memory mapped region. Must be released with FileMmap.destroy(). Provided File will not be closed"
*>
fn mmap::FileMmap? mmap_file(File file, usz offset = 0, usz len = 0, vm::VirtualMemoryAccess access = READ, bool shared = false)
{
	if (len == 0)
	{
		usz cur = file.seek(0, CURSOR)!;
		defer file.seek(cur, SET)!!;
		usz file_size = file.seek(0, END)!;
		len = file_size - offset;
	}

	// get the page size
	usz page_size = vm::aligned_alloc_size(0);

	// align the offset specified by the user (might be not aligned)
	usz page_offset = offset & (page_size - 1);
	usz map_offset = offset - page_offset;
	
	// adjust map length (both the region start and the region end might be not aligned)
	usz map_len = len + page_offset; // when region start not aligned
	map_len = vm::aligned_alloc_size(map_len); // when region end not aligned

	void* ptr = vm::mmap_file(file.fd(), map_len, map_offset, access, shared)!;

	// FileMmap does not own the supplied file
	return {{}, {ptr, map_len, access}, page_offset, len};
}

<*
 Maps a region of the given file into memory

 @param filename : "File path"
 @param mode : "File opening mode"
 @param offset : "Byte offset in file, will be rounded down to page size"
 @param len : "Size in bytes to map starting from offset, will be rounded up to page size"
 @return? mem::OUT_OF_MEMORY, vm::ACCESS_DENIED, vm::RANGE_OVERFLOW, vm::INVALID_ARGS, vm::UNKNOWN_ERROR, io::NO_PERMISSION, io::FILE_NOT_VALID, io::WOULD_BLOCK, io::FILE_NOT_FOUND
 @return "Memory mapped region. Must be released with FileMmap.destroy()"
*>
fn mmap::FileMmap? mmap_open(String filename, String mode, usz offset = 0, usz len = 0, vm::VirtualMemoryAccess access = READ, bool shared = false)
{
	File file = open(filename, mode)!;
	defer catch (void)file.close();
	FileMmap mm = mmap_file(file, offset, len, access, shared)!;

	// FileMmap owns the file and it will close it on destroy()
	mm.file = file;

	return mm;
}

