#+private
package os

import "core:sys/linux"

Read_Directory_Iterator_Impl :: struct {
	prev_fi:        File_Info,
	dirent_backing: []u8,
	dirent_buflen:  int,
	dirent_off:     int,
}

@(require_results)
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
	scan_entries :: proc(it: ^Read_Directory_Iterator, dfd: linux.Fd, entries: []u8, offset: ^int) -> (fd: linux.Fd, file_name: string) {
		for d in linux.dirent_iterate_buf(entries, offset) {
			file_name = linux.dirent_name(d)
			if file_name == "." || file_name == ".." {
				continue
			}

			file_name_cstr := cstring(raw_data(file_name))
			entry_fd, errno := linux.openat(dfd, file_name_cstr, {.NOFOLLOW, .PATH})
			if errno == .NONE {
				return entry_fd, file_name
			} else {
				read_directory_iterator_set_error(it, file_name, _get_platform_error(errno))
			}
		}

		return -1, ""
	}

	index = it.index
	it.index += 1

	dfd := linux.Fd(_fd(it.f))

	entries := it.impl.dirent_backing[:it.impl.dirent_buflen]
	entry_fd, file_name := scan_entries(it, dfd, entries, &it.impl.dirent_off)

	for entry_fd == -1 {
		if len(it.impl.dirent_backing) == 0 {
			it.impl.dirent_backing = make([]u8, 512, file_allocator())
		}

		loop: for {
			buflen, errno := linux.getdents(linux.Fd(dfd), it.impl.dirent_backing[:])
			#partial switch errno {
			case .EINVAL:
				delete(it.impl.dirent_backing, file_allocator())
				n := len(it.impl.dirent_backing) * 2
				it.impl.dirent_backing = make([]u8, n, file_allocator())
				continue
			case .NONE:
				if buflen == 0 {
					return
				}
				it.impl.dirent_off = 0
				it.impl.dirent_buflen = buflen
				entries = it.impl.dirent_backing[:buflen]
				break loop
			case:
				read_directory_iterator_set_error(it, name(it.f), _get_platform_error(errno))
				return
			}
		}

		entry_fd, file_name = scan_entries(it, dfd, entries, &it.impl.dirent_off)
	}
	defer linux.close(entry_fd)

	// PERF: reuse the fullpath string like on posix and wasi.
	file_info_delete(it.impl.prev_fi, file_allocator())

	err: Error
	fi, err = _fstat_internal(entry_fd, file_allocator())
	it.impl.prev_fi = fi

	if err != nil {
		temp_allocator := TEMP_ALLOCATOR_GUARD({})
		path, _ := _get_full_path(entry_fd, temp_allocator)
		read_directory_iterator_set_error(it, path, err)
	}

	ok = true
	return
}

_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
	// NOTE: Allow calling `init` to target a new directory with the same iterator.
	it.impl.dirent_buflen = 0
	it.impl.dirent_off = 0

	if f == nil || f.impl == nil {
		read_directory_iterator_set_error(it, "", .Invalid_File)
		return
	}

	stat: linux.Stat
	errno := linux.fstat(linux.Fd(fd(f)), &stat)
	if errno != .NONE {
		read_directory_iterator_set_error(it, name(f), _get_platform_error(errno))
		return
	}

	if (stat.mode & linux.S_IFMT) != linux.S_IFDIR {
		read_directory_iterator_set_error(it, name(f), .Invalid_Dir)
		return
	}
}

_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
	if it == nil {
		return
	}

	delete(it.impl.dirent_backing, file_allocator())
	file_info_delete(it.impl.prev_fi, file_allocator())
}
