#!/usr/bin/lua

--local debugging = true

--local baudRates = {300,600,1200,2400,4800,9600,19200,38400,57600,115200,230400,460800,921600,3200000,3686400,4000000}
local baudRates = {4000000,3686400,3200000,921600,460800,230400,115200,57600,38400,19200,9600,4800,2400,1200,600,300}

local escape_lua_pattern
    do
        local matches =
        {
            ["^"] = "%^";
            ["$"] = "%$";
            ["("] = "%(";
            [")"] = "%)";
            ["%"] = "%%";
            ["."] = "%.";
            ["["] = "%[";
            ["]"] = "%]";
            ["*"] = "%*";
            ["+"] = "%+";
            ["-"] = "%-";
            ["?"] = "%?";
            ["\0"] = "%z";
        }

        escape_lua_pattern = function(s)
        return (s:gsub(".", matches))
    end
end

-- Perform system command and return text output
function os.capture(cmd)
    local f = assert(io.popen(cmd, 'r'))
    local s = assert(f:read('*a'))
    f:close()
    return s
end

-- Read from file, if anything available
function clearRxBuffer(dev)
    os.execute("read -t 0 < "..dev)
end

-- Set tty baudrate (for accessing modem as file)
function sttyBaud(dev, baud)
    local command = "/bin/stty -F "..dev.." "..baud.." cread clocal -crtscts 2>/dev/null"
    local result = os.execute(command) or false
    return result
end

-- Read from modem with timeout
function rx(dev, timeout)
    local timeout = timeout or 0.1
    local reply = os.capture("timeout "..timeout.." cat "..dev.." 2>/dev/null") or ""
    return reply
end

-- Send and receive
function txrx(dev, msg, timeout)
    if (debugging) then print("D : "..msg) end
    local err = nil
    local devFile = nil
    devFile, err = io.open(dev, "w")
    if (devFile == nil) then
        print(err)
        os.exit(1)
    end
    
    devFile:write("\r"..msg.."\r")
    local reply = rx(dev, timeout):gsub(escape_lua_pattern(msg),"")
    if (debugging) then 
    for line in reply:gmatch("[^\r\n]+") do
            print("M : "..line)
        end
    end   
    devFile:close()
    return reply
end

-- Try to interrupt a data call
function interruptData(dev)
    if (debugging) then print("D : +++") end
    local err = nil
    local devFile = nil
    devFile, err = io.open(dev, "w")
    if (devFile == nil) then
        print(err)
        os.exit(1)
    end
    
    os.execute("sleep 1")
    devFile:write("+++")
    local reply = rx(dev, 1.1)
    if (debugging) then 
    for line in reply:gmatch("[^\r\n]+") do
            print("M : "..line)
        end
    end   
    devFile:close()
    if reply:match("OK%s+") then
        return true
    else
        return false
    end
end

-- Get or set modem baudrate
function IPR(dev, baud)
    if (baud == nil) then
        local reply = txrx(dev, "AT+IPR?")
        local baud = reply:match("%+IPR:%s-(%d+)%s+OK%s+") or ""
        return tonumber(baud)
    else
        local reply = txrx(dev, "AT+IPR="..baud)
        if (reply:match("OK%s+")) then
            return true
        else
            return false
        end
    end
end

-- Get modem info (mode, manufacturer, etc.) if available
function getModemInfo(dev)
    local reply = txrx(dev, "ATI")
    reply = reply:match("%s+(.-)%s+OK%s+") or ""
    return reply
end

-- Get supported baudrates for modem
function getSupportedBaud(dev)
    local reply = txrx(dev, "AT+IPR=?")
    reply = reply:match("%+IPR:%s+(.-)%s+OK%s+") or ""
    local bauds = {}
    for b in reply:gmatch("(%d+),?") do
        table.insert(bauds, b)
    end
    return bauds
end

-- Attention
function AT(dev)
    local reply = txrx(dev, "AT")
    if reply:match("OK%s+") then
        return true
    else
        return false
    end
end

-- Attention with reset
function ATZ(dev)
    local reply = txrx(dev, "ATZ")
    if reply:match("OK%s+") then
        return true
    else
        return false
    end
end

-- Set modem echo
function ATE(dev, state)
    local state = state or ""
    local reply = txrx(dev, "ATE"..state)
    if reply:match("OK%s+") then
        return true
    else
        return false
    end
end

-- Is this baudrate in our list of supported baudrates?
function validBaud(baud)
    for idx, b in pairs(baudRates) do
        if (b == baud) then return true end
    end
    return false
end

-- Try to get modem's attention, one baudrate at a time
function baudScan(dev, baud)
    for i,b in pairs(baudRates) do
        if (b ~= baud) then
            sttyBaud(dev, b)
            clearRxBuffer(dev)
            io.write("Trying "..b.." bps ... ")
            if (ATZ(dev)) then
                io.write("OK!\n")
                return b
            else
                io.write("failed\n")
            end
        end
    end
    return nil
end

-- JUST LIKE C, LOOOOOOL ROFLMAO XD
function main()
    local dev = nil
    local baud = nil
    local pin = nil
    local apn = nil
    
    if (#(arg) == 0) then
        config = os.capture("cat /etc/ppp/luadial.conf") or ""
        for line in config:gmatch("[^\r\n]+") do
            line = line:gsub("%s-#.+","") or ""
            if (line ~= "") then
                --print(">"..line)
                dev = line:match("dev%s-=%s-(.+)%s-") or dev
                baud = line:match("baud%s-=%s-(.+)%s-") or baud
                pin = line:match("pin%s-=%s-(.+)%s-") or pin
                apn = line:match("apn%s-=%s-(.+)%s-") or apn
            end
        end
        if (dev == nil) or (baud == nil) then
            print("Invalid or incomplete config")
            os.exit(1)
        end
        baud = tonumber(baud)
        if (validBaud(baud) == false) then
            print("Invalid baudrate: ("..baud..") bps")
            os.exit(1)
        end
    elseif (#(arg) == 2) then
        dev = arg[1]
        baud = tonumber(arg[2])
        if (validBaud(baud) == false) then
            print("Invalid baudrate: ("..baud..") bps")
            os.exit(1)
        end
    else
        print("Unsupported argument(s)")
        os.exit(1)
    end
   
    sttyBaud(dev, baud)
    clearRxBuffer(dev)
    
    local success = false
    io.write("Trying "..dev.." @ "..baud.." bps ... ")
    -- Initial attempt at original baud --
    if (ATZ(dev)) then
        io.write("OK!\n")
        success = true
    else
        io.write("failed\n")
        io.write("Attempt data disconnect on next retries\n")
        -- Try +++ to get out of data mode --
        for attempts = 2,3 do
            io.write("Trying "..baud.." bps ... ")
            -- Interrupt and reset data call
            if (interruptData(dev) and ATZ(dev)) then
                io.write("OK!\n")
                success = true
                break
            else
                io.write("failed\n")
            end
        end        
    end
    
    -- Oh no!
    if (success == false) then
        io.write("Attempt baudrate scan on next retries\n")
        if (baudScan(dev, baud) == nil) then
            io.write("No response, giving up\n")
            os.exit(1)
        end
    end

    -- Double check the baudrate that the modem is reporting
    local modemBaud = IPR(dev)
    -- Modem is operating at a different baud than we want it to
    if (baud ~= modemBaud) then
        io.write("Changing modem baudrate to "..baud.." bps ... ")
        if (IPR(dev, baud)) then
            io.write("OK!\n")
            sttyBaud(dev, baud)
            clearRxBuffer(dev)
        else
            io.write("failed\n")
        end
    end    
end

main()
