#!/usr/bin/lua

local posix = require 'posix'
local invoker = require 'invoker'

-- global variables
local g_ttydev = nil   -- path of controlling ttyUSB for 4G modem
local moudle_type = nil
local renum = 5
local gfmt = string.format

local function ts_init()
    if posix.access('/usr/bin/lua') == 0 then
        posix.setenv('PATH', '/opt/lnxall_app/bin:/usr/bin:/usr/sbin:/bin:/sbin', true)
    end
    return true
end

local function judge_moudle()
    okay, outbuf = invoker.invoke(invoker.OUTPUT, "lsusb")
    if okay ~= 0 or type(outbuf) ~= "string" or string.len(outbuf) == 0 then
        io.stderr:write("Error, failed to invoke `lsusb!\n")
        io.stderr:flush()
        return false
    end

    -- process line by line
    for output in string.gmatch(outbuf, "([^\r\n]+)") do
        usbid = string.match(output, "(%x%x%x%x:%x%x%x%x)")
        if string.find(usbid,"2c7c") then
            moudle_type = "Quectel"
            return usbid
        end
        if string.find(usbid,"1782") or string.find(usbid,"19d1") then
            moudle_type = "AirM2M"
            return usbid
        end
		moudle_type = "no_modem"
    end

    if not usbid then
        io.stderr:write("Warning, no 4G network modem found!\n")
        io.stderr:flush()
        return false
    end
end

local function get_vid_pid(device_path) -- get the Vendor and Product ID from the real device path
    local vid, pid
    local path = device_path
        if path == nil then
                return nil, nil
        end
    repeat
        local uevent_file = io.open(path .. "/uevent")  -- make sure the V and P id is saved in uevent, of course uedvadm also will get the information from uevent file
        if uevent_file then
            for line in uevent_file:lines() do
                local key, value = line:match("(%a+)=(.+)")
                if key == "PRODUCT" then
                    vid, pid = value:match("(%x+)%/(%x+)%/(%x+)")
                    if vid and pid then
                        vid = string.format("%04x", tonumber(vid, 16))
                        pid = string.format("%04x", tonumber(pid, 16))
                        return vid .. ":" .. pid
                    end
                end
            end
            uevent_file:close()
        end
        path = path:match("(.+)/[^/]+") -- if not found, next step is to check the parent directory.
    until not path or path == ""
    return nil, nil
end


local function find_device_path(device_name)
    local device_path
    -- unfortunately, this method is strong depends on kernel version. As far as I know it can't work on 2.4 kernel.
    for class in io.popen("ls " .. "/sys/class/"):lines() do
        local path = "/sys/class/" .. class .. "/" .. device_name

        if io.open(path) then
            local link = io.popen("readlink -f " .. path):read("*line") --  get the link path
            if link then
                while link:match("%.%./") do
                    link = link:gsub("/[^/]+/%.%./", "/") -- get the real path
                end
                device_path = link
                                break
            end
        end
    end
    return device_path
end


local function sleep()
        local file = io.popen("sleep 1")
        file:close()
end

local function operating_serial_device (dev_name, command)
    os.execute("stty -F " .. dev_name .. " raw cs8")
    -- open device with nonblock mode.
    local serial_device = posix.open(dev_name, posix.O_RDWR+posix.O_NONBLOCK+posix.O_NOCTTY)
    command = "\r" .. command .. "\r\n"
    if serial_device then
        posix.write(serial_device, command)
        sleep()
        local data = posix.read (serial_device, 256)
        posix.close(serial_device)
        if command == "\rAT+CGSN\r\n" then
			if #string.match(data,"%d+") < 15 then
                if renum > 0 then
                    renum = renum - 1
                    data = operating_serial_device(g_ttydev,"AT+CGSN")
                else
                    return nil
                end
            else
                renum = 5
                data = "imei info: " .. string.match(data,"%d+") .. "\n"
            end
        end
        if command == "\rAT+CCID\r\n" then
            if #string.match(data,"+CCID:%s*(%S+)") < 20 then
                if renum > 0 then
                    renum = renum - 1
                    data = operating_serial_device(g_ttydev,"AT+CCID")
                else
                    return nil
                end
            else
                renum = 5
                data = "iccid info: " .. string.match(data,"+CCID:%s*(%S+)") .. "\n"
            end
        end
		if command == "\rAT+CSQ\r\n" then
            data = "signal info: " .. string.match(data,"%d+") .. "\n"
		end
        io.stdout:flush();io.stderr:flush()
        io.stdout:write(data)
        return data
    end
    return nil
end

local function moudle_reply()
        local jdx = 0
        local usbid = judge_moudle()
        if usbid then
            while jdx < 3 do
                if string.find(usbid,"19d1") then
                    g_ttydev = find_device_path (gfmt ("ttyACM%d", jdx))
                else
                    g_ttydev = find_device_path (gfmt ("ttyUSB%d", jdx))
                end
                local vpid = get_vid_pid (g_ttydev)
                if vpid == usbid then
					if posix.access(g_ttydev) == 0 then
						if string.find(usbid,"19d1") then
							g_ttydev = gfmt("/dev/ttyACM%d", jdx)
                        else
							g_ttydev = gfmt("/dev/ttyUSB%d", jdx)
                        end
                        local info = posix.stat(g_ttydev)
						if type(info) == "table" and info.st_mode and info.st_mode == 8576 then
							local reply = operating_serial_device(g_ttydev,"AT")
                            if string.find(reply,"OK") and moudle_type == "Quectel" then
								operating_serial_device(g_ttydev,"ATI")
                                return true
                            end
                            if string.find(reply,"OK") and moudle_type == "AirM2M" then
								operating_serial_device(g_ttydev,"AT+CGMM")
                                return true
                            end
                        end
                    end
                end
                jdx = jdx + 1
            end
        else
            return false
        end
end

local function ts_main()
        flag = ts_init()
        reply_flag = moudle_reply()
        if flag and reply_flag then
            io.stdout:write("Producer: " .. '"' .. moudle_type ..'"'.. "\n")
            operating_serial_device(g_ttydev,"AT+COPS?")
            operating_serial_device(g_ttydev,"AT+CGSN")
            operating_serial_device(g_ttydev,"AT+CCID")
			operating_serial_device(g_ttydev,"AT+CSQ")
            return true
        else
            io.stdout:write("Producer: " .. '"' .. moudle_type ..'"'.. "\n")
            return false
        end

end

ts_main()
os.exit(0)
