-- vim: ft=terra
-- TODO: add support for windows IO calls
local handle_type = int
local posix = terralib.includec 'fcntl.h'
local unistd = terralib.includec 'unistd.h'
local mm = terralib.includec 'sys/mman.h'

struct file {
	handle: handle_type
	read: bool
	write: bool
}

file.mode = { read = 0, write = 1, rw = 2 }
file.seek = { abs = 0, ofs = 1, eof = 2 }

file.methods = {
	open = terra(path: rawstring, mode: uint8)
		var f: file
		var flag: int
		if mode == [file.mode.rw] then
			flag = posix.O_RDWR
			f.read = true f.write = true
		elseif mode == [file.mode.read] then
			flag = posix.O_RDONLY
			f.read = true f.write = false
		elseif mode == [file.mode.read] then
			flag = posix.O_WRONLY
			f.read = false f.write = true
		else lib.bail('invalid file mode') end
		lib.dbg('opening file ', path)
		f.handle = posix.open(path, flag) 

		var r: lib.stat(file)
		if f.handle == -1 then
			r.ok = false
			r.error = 1 -- TODO get errno somehow?
		else
			r.ok = true
			r.val = f
		end
		return r
	end;
	close = terra(self: &file)
		unistd.close(self.handle)
		self.handle = -1
		self.read = false
		self.write = false
	end;
	read = terra(self: &file, dest: rawstring, sz: intptr): ptrdiff
		return unistd.read(self.handle,dest,sz)
	end;
	write = terra(self: &file, data: &opaque, sz: intptr): ptrdiff
		return unistd.write(self.handle,data,sz)
	end;
	seek = terra(self: &file, ofs: ptrdiff, wh: int)
		var whence: int
		if wh == [file.seek.abs] then
			whence = unistd.SEEK_SET
		elseif wh == [file.seek.ofs] then
			whence = unistd.SEEK_CUR
		elseif wh == [file.seek.eof] then
			whence = unistd.SEEK_END
		else lib.bail('invalid seek mode') end
	
		return unistd.lseek(self.handle, ofs, whence)
	end;
}

terra file:len(): intptr
	var cur = self:seek(0, [file.seek.ofs])
	var sz = self:seek(0, [file.seek.eof])
	self:seek(cur, [file.seek.abs])
	return sz
end

local struct mapping {
	addr: &opaque
	sz: intptr
}
terra mapping:unmap()
	lib.dbg('releasing file mapping')
	return mm.munmap(self.addr, self.sz)
end
-- provide for syncing mechanism?

terra file:mapin(ofs: intptr, sz: intptr)
	var prot = 0
	if self.read then prot = mm.PROT_READ end 
	if self.write then prot = prot or mm.PROT_WRITE end
	if sz == 0 then sz = self:len() end
	lib.dbg('mapping file into memory')
	return mapping {
		addr = mm.mmap(nil, sz, prot, mm.MAP_PRIVATE, self.handle, ofs);
		sz = sz;
	}
end

return file
