#!/opt/lnxall_app/bin/lua

local invoker = require "invoker"
local gfmt = string.format

local function tty_clear(self)
	local tfd = self.fd
	-- consume all existing buffers
	while tfd do
		local res = invoker.readfd(fd, 2048)
		if not res or string.len(res) == 0 then
			break
		end
	end
	return true
end

local function tty_clear_error(self)
	self.error, self.errno = false, 0
	return true
end

local function tty_haserr(self)
	return self.error, self.errno
end

local function tty_hasreply(self, msec)
	local tfd = self.fd
	if type(tfd) ~= "number" or tfd < 0 then return nil end

	-- clear existing errors, if any
	self.error, self.errno = false, 0
	-- consume all lingering buffer
	while true do
		local res = invoker.readfd(tfd, 2048)
		if not res or string.len(res) == 0 then
			break
		end
	end

	local okay, errn = invoker.writefd(tfd, "AT\r\n")
	if not okay then
		self.error, self.errno = true, 5
		return nil
	end

	if type(msec) ~= "number" or msec < 500 then msec = 3000 end
	local replies, nres, now = {}, 0, invoker.uptimsec()
	msec = now + msec
	while now < msec  do
		invoker.msleep(300)
		local rdat = invoker.readfd(tfd, 2048)
		if rdat and #rdat > 0 then
			if string.find(rdat, "OK", 1, true) then return true end
			nres = nres + 1
			replies[nres] = rdat
		end
		now = invoker.uptimsec()
	end

	if nres >= 2 then
		replies = table.concat(replies)
		if string.find(replies, "OK", 1, true) then return true end
	end
	return false
end

local function tty_write(self, atcmd)
	local tfd = self.fd
	if not tfd then return nil end

	local atlen = type(atcmd) == "string" and string.len(atcmd) or 0
	if atlen <= 0 then return nil end

	-- write the AT command
	local okay, errn = invoker.writefd(tfd, atcmd)
	if not okay or atlen ~= okay then
		self["error"], self["errno"] = true, errn
		io.stderr:write(gfmt("Error, failed to write AT-command: %s, \n", atcmd))
		io.stderr:flush()
		return nil
	end
	return okay
end

local function tty_exists(self)
	local typn = invoker.statfile(self.tty)
	if typn == "character" then return true end
	return false
end

local function tty_readany(self, wait_msec, any0, any1, any2)
	local tfd = self.fd
	if type(wait_msec) ~= "number" or wait_msec <= 300 then
		wait_msec = 300
	end

	local replies, rnum = {}, 0
	local now = invoker.uptimsec()
	local endt = now + wait_msec

	while now < endt do
		invoker.msleep(120)
		local rdat = invoker.readfd(tfd, 2048)
		if rdat and #rdat > 0 then
			rnum = rnum + 1
			replies[rnum] = rdat
			if any0 and string.find(rdat, any0, 1, true) then break end
			if any1 and string.find(rdat, any1, 1, true) then break end
			if any2 and string.find(rdat, any2, 1, true) then break end
			if any3 and string.find(rdat, any3, 1, true) then break end
		end
		now = invoker.uptimsec()
	end

	if rnum > 0 then
		if rnum >= 2 then return table.concat(replies) end
		return replies[1]
	end
	return nil
end

-- if `wait_reply is a positive integer, wait that many milliseconds for reply
local function tty_read(self, wait_reply)
	local reply, tfd = nil, self.fd

	if type(wait_reply) ~= "number" or wait_reply <= 0 then
		reply = invoker.readfd(tfd, 2048)
	else
		if wait_reply < 300 then wait_reply = 300 end
		local replies, rnum = {}, 0
		local now = invoker.uptimsec()
		local endt = now + wait_reply

		while now < endt do
			invoker.msleep(130)
			local rdat = invoker.readfd(tfd, 2048)
			if rdat and #rdat > 0 then
				rnum = rnum + 1
				replies[rnum] = rdat
			end
			now = invoker.uptimsec()
		end

		if rnum > 0 then reply = table.concat(replies) end
	end
	return reply
end

local function tty_close(self)
	if type(self) ~= "table" then return false end
	local tfd = self.fd
	self.fd, self.tty = nil, nil
	self.error, self.errno = nil, nil
	if type(tfd) == "number" and tfd >= 0 then
		invoker.closefd(tfd); tfd = nil
	end
	setmetatable(self, nil)
	self = nil
end

local tty_metatable = {
	["clear"] = tty_clear, ["haserr"] = tty_haserr,
	["hasreply"] = tty_hasreply, ["clear_error"] = tty_clear_error,
	["write"] = tty_write, ["readany"] = tty_readany,
	["read"] = tty_read, ["close"] = tty_close, ["exists"] = tty_exists }

local function opentty(ttypath, stty)
	local typn = invoker.statfile(ttypath)
	if typn ~= "character" then
		local tpath = ttypath
		if type(tpath) ~= "string" then tpath = "nil" end
		io.stderr:write(gfmt("Error, tty not found or invalid: %s\n", tpath))
		io.stderr:flush()
		return nil
	end

	-- update tty serial settings
	if stty then
		invoker.invoke(invoker.NOSTDIO, 'stty', '-F', ttypath, "raw", "cs8", "-hupcl",
			"-onlcr", "-iexten", "-echo", "-echoe", "-echok", "-echoctl", "-echoke")
	end

	local oflags = 526594 -- O_RDWR | O_NONBLOCK | O_NOCTTY | O_CLOEXEC
	local fd, errn = invoker.open(ttypath, oflags)
	if not fd then
		if type(errn) ~= "number" then errn = 2025 end
		io.stderr:write(gfmt("Error, failed to open(%s): %d\n", ttypath, errn))
		io.stderr:flush()
		return nil
	end

	local ttyobj = { ["fd"] = fd, ["tty"] = ttypath, ["error"] = false, ["errno"] = 0 }
	setmetatable(ttyobj, { __index = tty_metatable, __gc = tty_close })
	return ttyobj
end

return { ["opentty"] = opentty, }
