#!/usr/bin/env lua

--[[
 * A lua library to manipulate mtk's wifi driver. used in luci-app-mtk.
 *
 * Hua Shao <nossiac@163.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
]]

local mtkwifi = {}
local _, nixio = pcall(require, "nixio")

function mtkwifi.debug(...)
    local ff = io.open("/tmp/mtkwifi.dbg.log", "a")
    local vars = {...}
    for _, v in pairs(vars) do
        ff:write(v.." ")
    end
    ff:write("\n")
    ff:close()
end

if not nixio then
    nixio.syslog = mtkwifi.debug
end

function string:split(sep)
    local sep, fields = sep or ":", {}
    local pattern = string.format("([^%s]+)", sep)
    self:gsub(pattern, function(c) fields[#fields+1] = c end)
    return fields
end

function mtkwifi.__trim(s)
  if s then return (s:gsub("^%s*(.-)%s*$", "%1")) end
end

function mtkwifi.__handleSpecialChars(s)
    s = s:gsub("\\", "\\\\")
    s = s:gsub("\"", "\\\"")
    s = mtkwifi.__trim(s)
    return s
end

-- if order function given, sort by it by passing the table and keys a, b,
-- otherwise just sort the keys 
function mtkwifi.__spairs(t, order)
    -- collect the keys
    local keys = {}
    for k in pairs(t) do keys[#keys+1] = k end
        table.sort(keys, order)
        -- return the iterator function
        local i = 0
        return function()
            i = i + 1
            if keys[i] then
                return keys[i], t[keys[i]]
            end
    end
end

function mtkwifi.__lines(str)
    local t = {}
    local function helper(line) table.insert(t, line) return "" end
    helper((str:gsub("(.-)\r?\n", helper)))
    return t
end

function mtkwifi.__get_l1dat()
    if not pcall(require, "l1dat_parser") then
        return
    end

    local parser = require("l1dat_parser")
    local l1dat = parser.load_l1_profile(parser.L1_DAT_PATH)

    return l1dat, parser
end

function mtkwifi.sleep(s)
    local ntime = os.clock() + s
    repeat until os.clock() > ntime
end

function mtkwifi.deepcopy(orig)
    local orig_type = type(orig)
    local copy
    if orig_type == 'table' then
        copy = {}
        for orig_key, orig_value in next, orig, nil do
            copy[mtkwifi.deepcopy(orig_key)] = mtkwifi.deepcopy(orig_value)
        end
        setmetatable(copy, mtkwifi.deepcopy(getmetatable(orig)))
    else -- number, string, boolean, etc
        copy = orig
    end
    return copy
end

function mtkwifi.read_pipe(pipe)
    local fp = io.popen(pipe)
    local txt =  fp:read("*a")
    fp:close()
    return txt
end

function mtkwifi.load_profile(path, raw)
    local cfgs = {}
    local content

    if path then
    local fd = io.open(path, "r")
    if not fd then return end
        content = fd:read("*all")
        fd:close()
    elseif raw then
        content = raw
    else
        return
    end

    -- convert profile into lua table
    for _,line in ipairs(mtkwifi.__lines(content)) do
        line = mtkwifi.__trim(line)
        if string.byte(line) ~= string.byte("#") then
            local i = string.find(line, "=")
            if i then
                local k,v
                k = string.sub(line, 1, i-1)
                v = string.sub(line, i+1)
                if cfgs[mtkwifi.__trim(k)] then
                    mtkwifi.debug("warning", "skip repeated key"..line)
                end
                cfgs[mtkwifi.__trim(k)] = mtkwifi.__trim(v) or ""
            else
                mtkwifi.debug("warning", "skip line without '=' "..line)
            end
        else
            mtkwifi.debug("warning", "skip comment line "..line)
        end
    end
    return cfgs
end

function mtkwifi.__profile_bak_path(profile)
    local bak = "/tmp/mtk/wifi/"..string.match(profile, "([^/]+\.dat)")..".last"
    os.execute("mkdir -p /tmp/mtk/wifi")
    return bak
end

function mtkwifi.save_profile(cfgs, path)

    if not cfgs then
        mtkwifi.debug("configuration was empty, nothing saved")
        return
    end

    local fd = io.open(path, "w")
    table.sort(cfgs, function(a,b) return a<b end)
    if not fd then return end
    fd:write("# Generated by mtkwifi.lua\n")
    fd:write("Default\n")
    for k,v in mtkwifi.__spairs(cfgs, function(a,b) return string.upper(a) < string.upper(b) end) do
        fd:write(k.."="..v.."\n")
    end
    fd:close()

    if pcall(require, "mtknvram") then
        local nvram = require("mtknvram")
        local l1dat, l1 = mtkwifi.__get_l1dat()
        local zone = l1 and l1.l1_path_to_zone(path)

        if not l1dat then
            mtkwifi.debug("save_profile: no l1dat", path)
            nvram.nvram_save_profile(path)
        else
            if zone then
                mtkwifi.debug("save_profile:", path, zone)
                nvram.nvram_save_profile(path, zone)
            else
                mtkwifi.debug("save_profile:", path)
                nvram.nvram_save_profile(path)
            end
        end
    end
end

-- if path2 not give, we use path1's backup.
function mtkwifi.diff_profile(path1, path2)
    assert(path1)
    if not path2 then
        path2 = mtkwifi.__profile_bak_path(path1)
        if not mtkwifi.exists(path2) then
            return {}
        end
    end
    assert(path2)

    local diff = {}
    local cfg1 = mtkwifi.load_profile(path1) or {}
    local cfg2 = mtkwifi.load_profile(path2) or {}

    for k,v in pairs(cfg1) do
        if cfg2[k] ~= cfg1[k] then
            diff[k] = {cfg1[k] or "", cfg2[k] or ""}
        end
    end

    for k,v in pairs(cfg2) do
        if cfg2[k] ~= cfg1[k] then
            diff[k] = {cfg1[k] or "", cfg2[k] or ""}
        end
    end

    return diff
end


-- Mode 12 and 13 are only available for STAs.
local WirelessModeList = {
    [0] = "B/G mixed",
    [1] = "B only",
    [2] = "A only",
    -- [3] = "A/B/G mixed",
    [4] = "G only",
    -- [5] = "A/B/G/GN/AN mixed",
    [6] = "N in 2.4G only",
    [7] = "G/GN", -- i.e., no CCK mode
    [8] = "A/N in 5 band",
    [9] = "B/G/GN mode",
    -- [10] = "A/AN/G/GN mode", --not support B mode
    [11] = "only N in 5G band",
    -- [12] = "B/G/GN/A/AN/AC mixed",
    -- [13] = "G/GN/A/AN/AC mixed", -- no B mode
    [14] = "A/AC/AN mixed",
    [15] = "AC/AN mixed", --but no A mode
    [16] = "AX/B/G/GN mode",
    [17] = "AX/AC/AN mixed", 
}

local DevicePropertyMap = {
    -- 2.4G
    {device="MT7603", band={"0", "1", "4", "6", "7", "9"}},
    {device="MT7620", band={"0", "1", "4", "6", "7", "9"}},
    {device="MT7622", band={"0", "1", "4", "9"}},
    {device="MT7628", band={"0", "1", "4", "6", "7", "9"}},
    -- 5G
    {device="MT7610", band={"2", "8", "11", "14", "15"}},
    {device="MT7612", band={"2", "8", "11", "14", "15"}},
    {device="MT7662", band={"2", "8", "11", "14", "15"}},
    -- Mix
    {device="MT7615", band={"0", "1", "4", "9", "2", "8", "14", "15","16","17"}}
}

mtkwifi.CountryRegionList_5G_All = {
    {region=0, text="0: Ch36~64, Ch149~165"},
    {region=1, text="1: Ch36~64, Ch100~140"},
    {region=2, text="2: Ch36~64"},
    {region=3, text="3: Ch52~64, Ch149~161"},
    {region=4, text="4: Ch149~165"},
    {region=5, text="5: Ch149~161"},
    {region=6, text="6: Ch36~48"},
    {region=7, text="7: Ch36~64, Ch100~140, Ch149~165"},
    {region=8, text="8: Ch52~64"},
    {region=9, text="9: Ch36~64, Ch100~116, Ch132~140, Ch149~165"},
    {region=10, text="10: Ch36~48, Ch149~165"},
    {region=11, text="11: Ch36~64, Ch100~120, Ch149~161"},
    {region=12, text="12: Ch36~64, Ch100~144"},
    {region=13, text="13: Ch36~64, Ch100~144, Ch149~165"},
    {region=14, text="14: Ch36~64, Ch100~116, Ch132~144, Ch149~165"},
    {region=15, text="15: Ch149~173"},
    {region=16, text="16: Ch52~64, Ch149~165"},
    {region=17, text="17: Ch36~48, Ch149~161"},
    {region=18, text="18: Ch36~64, Ch100~116, Ch132~140"},
    {region=19, text="19: Ch56~64, Ch100~140, Ch149~161"},
    {region=20, text="20: Ch36~64, Ch100~124, Ch149~161"},
    {region=21, text="21: Ch36~64, Ch100~140, Ch149~161"},
    {region=22, text="22: Ch100~140"},
    {region=30, text="30: Ch36~48, Ch52~64, Ch100~140, Ch149~165"},
    {region=31, text="31: Ch52~64, Ch100~140, Ch149~165"},
    {region=32, text="32: Ch36~48, Ch52~64, Ch100~140, Ch149~161"},
    {region=33, text="33: Ch36~48, Ch52~64, Ch100~140"},
    {region=34, text="34: Ch36~48, Ch52~64, Ch149~165"},
    {region=35, text="35: Ch36~48, Ch52~64"},
    {region=36, text="36: Ch36~48, Ch100~140, Ch149~165"},
    {region=37, text="37: Ch36~48, Ch52~64, Ch149~165, Ch173"},
}

mtkwifi.CountryRegionList_2G_All = {
    {region=0, text="0: Ch1~11"},
    {region=1, text="1: Ch1~13"},
    {region=2, text="2: Ch10~11"},
    {region=3, text="3: Ch10~13"},
    {region=4, text="4: Ch14"},
    {region=5, text="5: Ch1~14"},
    {region=6, text="6: Ch3~9"},
    {region=7, text="7: Ch5~13"},
    {region=31, text="31: Ch1~11, Ch12~14"},
    {region=32, text="32: Ch1~11, Ch12~13"},
    {region=33, text="33: Ch1~14"},
}

mtkwifi.ChannelList_5G_All = {
    {channel=0,  text="Channel 0 (Auto )", region={}},
    {channel= 36, text="Channel  36 (5.180 GHz)", region={[0]=1, [1]=1, [2]=1, [6]=1, [7]=1, [9]=1, [10]=1, [11]=1, [12]=1, [13]=1, [14]=1, [17]=1, [18]=1, [20]=1, [21]=1, [30]=1, [32]=1, [33]=1, [34]=1, [35]=1, [36]=1, [37]=1}},
    {channel= 40, text="Channel  40 (5.200 GHz)", region={[0]=1, [1]=1, [2]=1, [6]=1, [7]=1, [9]=1, [10]=1, [11]=1, [12]=1, [13]=1, [14]=1, [17]=1, [18]=1, [20]=1, [21]=1, [30]=1, [32]=1, [33]=1, [34]=1, [35]=1, [36]=1, [37]=1}},
    {channel= 44, text="Channel  44 (5.220 GHz)", region={[0]=1, [1]=1, [2]=1, [6]=1, [7]=1, [9]=1, [10]=1, [11]=1, [12]=1, [13]=1, [14]=1, [17]=1, [18]=1, [20]=1, [21]=1, [30]=1, [32]=1, [33]=1, [34]=1, [35]=1, [36]=1, [37]=1}},
    {channel= 48, text="Channel  48 (5.240 GHz)", region={[0]=1, [1]=1, [2]=1, [6]=1, [7]=1, [9]=1, [10]=1, [11]=1, [12]=1, [13]=1, [14]=1, [17]=1, [18]=1, [20]=1, [21]=1, [30]=1, [32]=1, [33]=1, [34]=1, [35]=1, [36]=1, [37]=1}},
    {channel= 52, text="Channel  52 (5.260 GHz)", region={[0]=1, [1]=1, [2]=1, [3]=1, [7]=1, [8]=1, [9]=1, [11]=1, [12]=1, [13]=1, [14]=1, [16]=1, [18]=1, [20]=1, [21]=1, [30]=1, [31]=1, [32]=1, [33]=1, [34]=1, [35]=1, [37]=1}},
    {channel= 56, text="Channel  56 (5.280 GHz)", region={[0]=1, [1]=1, [2]=1, [3]=1, [7]=1, [8]=1, [9]=1, [11]=1, [12]=1, [13]=1, [14]=1, [16]=1, [18]=1, [19]=1, [20]=1, [21]=1, [30]=1, [31]=1, [32]=1, [33]=1, [34]=1, [35]=1, [37]=1}},
    {channel= 60, text="Channel  60 (5.300 GHz)", region={[0]=1, [1]=1, [2]=1, [3]=1, [7]=1, [8]=1, [9]=1, [11]=1, [12]=1, [13]=1, [14]=1, [16]=1, [18]=1, [19]=1, [20]=1, [21]=1, [30]=1, [31]=1, [32]=1, [33]=1, [34]=1, [35]=1, [37]=1}},
    {channel= 64, text="Channel  64 (5.320 GHz)", region={[0]=1, [1]=1, [2]=1, [3]=1, [7]=1, [8]=1, [9]=1, [11]=1, [12]=1, [13]=1, [14]=1, [16]=1, [18]=1, [19]=1, [20]=1, [21]=1, [30]=1, [31]=1, [32]=1, [33]=1, [34]=1, [35]=1, [37]=1}},
    {channel=100, text="Channel 100 (5.500 GHz)", region={[1]=1, [7]=1, [9]=1, [11]=1, [12]=1, [13]=1, [14]=1, [18]=1, [19]=1, [20]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}},
    {channel=104, text="Channel 104 (5.520 GHz)", region={[1]=1, [7]=1, [9]=1, [11]=1, [12]=1, [13]=1, [14]=1, [18]=1, [19]=1, [20]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}},
    {channel=108, text="Channel 108 (5.540 GHz)", region={[1]=1, [7]=1, [9]=1, [11]=1, [12]=1, [13]=1, [14]=1, [18]=1, [19]=1, [20]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}},
    {channel=112, text="Channel 112 (5.560 GHz)", region={[1]=1, [7]=1, [9]=1, [11]=1, [12]=1, [13]=1, [14]=1, [18]=1, [19]=1, [20]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}},
    {channel=116, text="Channel 116 (5.580 GHz)", region={[1]=1, [7]=1, [9]=1, [11]=1, [12]=1, [13]=1, [14]=1, [18]=1, [19]=1, [20]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}},
    {channel=120, text="Channel 120 (5.600 GHz)", region={[1]=1, [7]=1, [11]=1, [12]=1, [13]=1, [19]=1, [20]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}},
    {channel=124, text="Channel 124 (5.620 GHz)", region={[1]=1, [7]=1, [12]=1, [13]=1, [19]=1, [20]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}},
    {channel=128, text="Channel 128 (5.640 GHz)", region={[1]=1, [7]=1, [12]=1, [13]=1, [19]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}},
    {channel=132, text="Channel 132 (5.660 GHz)", region={[1]=1, [7]=1, [9]=1, [12]=1, [13]=1, [14]=1, [18]=1, [19]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}},
    {channel=136, text="Channel 136 (5.680 GHz)", region={[1]=1, [7]=1, [9]=1, [12]=1, [13]=1, [14]=1, [18]=1, [19]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}},
    {channel=140, text="Channel 140 (5.700 GHz)", region={[1]=1, [7]=1, [9]=1, [12]=1, [13]=1, [14]=1, [18]=1, [19]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}},
    {channel=144, text="Channel 144 (5.720 GHz)", region={[12]=1, [13]=1, [14]=1}},
    {channel=149, text="Channel 149 (5.745 GHz)", region={[0]=1, [3]=1, [4]=1, [5]=1, [7]=1, [9]=1, [10]=1, [11]=1, [13]=1, [14]=1, [15]=1, [16]=1, [17]=1, [19]=1, [20]=1, [21]=1, [30]=1, [31]=1, [32]=1, [34]=1, [36]=1, [37]=1}},
    {channel=153, text="Channel 153 (5.765 GHz)", region={[0]=1, [3]=1, [4]=1, [5]=1, [7]=1, [9]=1, [10]=1, [11]=1, [13]=1, [14]=1, [15]=1, [16]=1, [17]=1, [19]=1, [20]=1, [21]=1, [30]=1, [31]=1, [32]=1, [34]=1, [36]=1, [37]=1}},
    {channel=157, text="Channel 157 (5.785 GHz)", region={[0]=1, [3]=1, [4]=1, [5]=1, [7]=1, [9]=1, [10]=1, [11]=1, [13]=1, [14]=1, [15]=1, [16]=1, [17]=1, [19]=1, [20]=1, [21]=1, [30]=1, [31]=1, [32]=1, [34]=1, [36]=1, [37]=1}},
    {channel=161, text="Channel 161 (5.805 GHz)", region={[0]=1, [3]=1, [4]=1, [5]=1, [7]=1, [9]=1, [10]=1, [11]=1, [13]=1, [14]=1, [15]=1, [16]=1, [17]=1, [19]=1, [20]=1, [21]=1, [30]=1, [31]=1, [32]=1, [34]=1, [36]=1, [37]=1}},
    {channel=165, text="Channel 165 (5.825 GHz)", region={[0]=1, [4]=1, [7]=1, [9]=1, [10]=1, [13]=1, [14]=1, [15]=1, [16]=1, [30]=1, [31]=1, [34]=1, [36]=1, [37]=1}},
    {channel=169, text="Channel 169 (5.845 GHz)", region={[15]=1}},
    {channel=173, text="Channel 173 (5.865 GHz)", region={[15]=1, [37]=1}},
}

mtkwifi.ChannelList_2G_All = {
    {channel=0, text="Channel 0 (Auto )", region={}},
    {channel= 1, text="Channel  1 (2412 GHz)", region={[0]=1, [1]=1, [5]=1, [31]=1, [32]=1, [33]=1}},
    {channel= 2, text="Channel  2 (2417 GHz)", region={[0]=1, [1]=1, [5]=1, [31]=1, [32]=1, [33]=1}},
    {channel= 3, text="Channel  3 (2422 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [31]=1, [32]=1, [33]=1}},
    {channel= 4, text="Channel  4 (2427 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [31]=1, [32]=1, [33]=1}},
    {channel= 5, text="Channel  5 (2432 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1, [31]=1, [32]=1, [33]=1}},
    {channel= 6, text="Channel  6 (2437 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1, [31]=1, [32]=1, [33]=1}},
    {channel= 7, text="Channel  7 (2442 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1, [31]=1, [32]=1, [33]=1}},
    {channel= 8, text="Channel  8 (2447 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1, [31]=1, [32]=1, [33]=1}},
    {channel= 9, text="Channel  9 (2452 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1, [31]=1, [32]=1, [33]=1}},
    {channel=10, text="Channel 10 (2457 GHz)", region={[0]=1, [1]=1, [2]=1, [3]=1, [5]=1, [7]=1, [31]=1, [32]=1, [33]=1}},
    {channel=11, text="Channel 11 (2462 GHz)", region={[0]=1, [1]=1, [2]=1, [3]=1, [5]=1, [7]=1, [31]=1, [32]=1, [33]=1}},
    {channel=12, text="Channel 12 (2467 GHz)", region={[1]=1, [3]=1, [5]=1, [7]=1, [31]=1, [32]=1, [33]=1}},
    {channel=13, text="Channel 13 (2472 GHz)", region={[1]=1, [3]=1, [5]=1, [7]=1, [31]=1, [32]=1, [33]=1}},
    {channel=14, text="Channel 14 (2477 GHz)", region={[4]=1, [5]=1, [31]=1, [33]=1}},
}

mtkwifi.ChannelList_5G_2nd_80MHZ_ALL = {
    {channel=36, text="Ch36(5.180 GHz) - Ch48(5.240 GHz)", chidx=2},
    {channel=52, text="Ch52(5.260 GHz) - Ch64(5.320 GHz)", chidx=6},
    {channel=-1, text="Channel between 64 100",  chidx=-1},
    {channel=100, text="Ch100(5.500 GHz) - Ch112(5.560 GHz)", chidx=10},
    {channel=112, text="Ch116(5.580 GHz) - Ch128(5.640 GHz)", chidx=14},
    {channel=-1, text="Channel between 128 132", chidx=-1},
    {channel=132, text="Ch132(5.660 GHz) - Ch144(5.720 GHz)", chidx=18},
    {channel=-1, text="Channel between 144 149", chidx=-1},
    {channel=149, text="Ch149(5.745 GHz) - Ch161(5.805 GHz)", chidx=22},
}

local AuthModeList = {
    "Disable",
    "OPEN",--OPENWEP
    "SHARED",--SHAREDWEP
    "WEPAUTO",
    "WPA2",
    "WPA2PSK",
    "WPAPSKWPA2PSK",
    "WPA1WPA2",
    "IEEE8021X",
}

local WpsEnableAuthModeList = {
    "Disable",
    "OPEN",--OPENWEP
    "WPA2PSK",
    "WPAPSKWPA2PSK",
}

local ApCliAuthModeList = {
    "Disable",
    "OPEN",
    "SHARED",
    "WPAPSK",
    "WPA2PSK",
    "WPAPSKWPA2PSK",
    -- "WPA",
    -- "WPA2",
    -- "WPAWPA2",
    -- "8021X",
}

local WPA_Enc_List = {
    "AES",
    "TKIP",
    "TKIPAES",
}


local WEP_Enc_List = {
    "WEP",
}

local dbdc_prefix = {
    {"ra",  "rax"},
    {"rai", "ray"},
    {"rae", "raz"},
}

local dbdc_apcli_prefix = {
    {"apcli",  "apclix"},
    {"apclii", "apcliy"},
    {"apclie", "apcliz"},
}

function mtkwifi.band(mode)
    local i = tonumber(mode)
    if i == 0
    or i == 1
    or i == 4
    or i == 6
    or i == 7
    or i == 9 
    or i == 16 then
        return "2.4G"
    else
        return "5G"
    end
end


function mtkwifi.__cfg2list(str)
    -- delimeter == ";"
    local i = 1
    local list = {}
    for k in string.gmatch(str, "([^;]+)") do
        list[i] = k
        i = i + 1
    end
    return list
end

function mtkwifi.token_set(str, n, v)
    -- n start from 1
    -- delimeter == ";"
    if not str then return end
    local tmp = mtkwifi.__cfg2list(str)
    if type(v) ~= type("") and type(v) ~= type(0) then
        mtkwifi.debug("err", "invalid value type in token_set, "..type(v))
        return
    end
    if #tmp < tonumber(n) then
        for i=#tmp, tonumber(n) do
            if not tmp[i] then
                tmp[i] = v -- pad holes with v !
            end
        end
    else
        tmp[n] = v
    end
    return table.concat(tmp, ";")
end


function mtkwifi.token_get(str, n, v)
    -- n starts from 1
    -- v is the backup in case token n is nil
    if not str then return v end
    local tmp = mtkwifi.__cfg2list(str)
    return tmp[tonumber(n)] or v
end

function mtkwifi.search_dev_and_profile_orig()
    local dir = io.popen("ls /etc/wireless/")
    if not dir then return end
    local result = {}
    -- case 1: mt76xx.dat (best)
    -- case 2: mt76xx.n.dat (multiple card of same dev)
    -- case 3: mt76xx.n.nG.dat (case 2 plus dbdc and multi-profile, bloody hell....)
    for line in dir:lines() do
        -- mtkwifi.debug("debug", "scan "..line)
        local tmp = io.popen("find /etc/wireless/"..line.." -type f -name \"*.dat\"")
        for datfile in tmp:lines() do
            -- mtkwifi.debug("debug", "test "..datfile)

            repeat do
            -- for case 1
            local devname = string.match(datfile, "("..line..").dat")
            if devname then
                result[devname] = datfile
                -- mtkwifi.debug("debug", "yes "..devname.."="..datfile)
                break
            end
            -- for case 2
            local devname = string.match(datfile, "("..line.."%.%d)%.dat")
            if devname then
                result[devname] = datfile
                -- mtkwifi.debug("debug", "yes "..devname.."="..datfile)
                break
            end
            -- for case 3
            local devname = string.match(datfile, "("..line.."%.%d%.%dG)%.dat")
            if devname then
                result[devname] = datfile
                -- mtkwifi.debug("debug", "yes "..devname.."="..datfile)
                break
            end
            end until true
        end
    end

    for k,v in pairs(result) do
        mtkwifi.debug("debug", "search_dev_and_profile_orig: "..k.."="..v)
    end

    return result
end

function mtkwifi.search_dev_and_profile_l1()
    local l1dat = mtkwifi.__get_l1dat()

    if not l1dat then return end

    local result = {}
    local dbdc_2nd_if = ""

    for k, dev in ipairs(l1dat) do
        dbdc_2nd_if = mtkwifi.token_get(dev.main_ifname, 2, nil)
        if dbdc_2nd_if then
            result[dev["INDEX"].."."..dev["mainidx"]..".1"] = mtkwifi.token_get(dev.profile_path, 1, nil)
            result[dev["INDEX"].."."..dev["mainidx"]..".2"] = mtkwifi.token_get(dev.profile_path, 2, nil)
        else
            result[dev["INDEX"].."."..dev["mainidx"]] = dev.profile_path
        end
    end

    for k,v in pairs(result) do
        mtkwifi.debug("debug", "search_dev_and_profile_l1: "..k.."="..v)
    end

    return result
end

function mtkwifi.search_dev_and_profile()
    return mtkwifi.search_dev_and_profile_l1() or mtkwifi.search_dev_and_profile_orig()
end

function mtkwifi.__setup_vifs(cfgs, devname, mainidx, subidx)
    local l1dat, l1 = mtkwifi.__get_l1dat()
    local dridx = l1dat and l1.DEV_RINDEX

    local prefix
    local main_ifname
    local vifs = {}
    local dev_idx = ""


    prefix = l1dat and l1dat[dridx][devname].ext_ifname or dbdc_prefix[mainidx][subidx]

    dev_idx = string.match(devname, "(%w+)")

    vifs["__prefix"] = prefix
    if (cfgs.BssidNum == nil) then
        mtkwifi.debug("BssidNum configuration value not found.")
        mtkwifi.debug("debug","BssidNum configuration value not found.")
        return
    end

    for j=1,tonumber(cfgs.BssidNum) do
        vifs[j] = {}
        vifs[j].vifidx = j -- start from 1
        dev_idx = string.match(devname, "(%w+)")
        main_ifname = l1dat and l1dat[dridx][devname].main_ifname or dbdc_prefix[mainidx][subidx].."0"

        mtkwifi.debug("setup_vifs", prefix, dev_idx, mainidx, subidx)

        vifs[j].vifname = j == 1 and main_ifname or prefix..(j-1)
        if mtkwifi.exists("/sys/class/net/"..vifs[j].vifname) then
            local flags = tonumber(mtkwifi.read_pipe("cat /sys/class/net/"..vifs[j].vifname.."/flags 2>/dev/null")) or 0
            vifs[j].state = flags%2 == 1 and "up" or "down"
        end
        vifs[j].__ssid = cfgs["SSID"..j]
        vifs[j].__bssid = mtkwifi.read_pipe("cat /sys/class/net/"..prefix..(j-1).."/address 2>/dev/null") or "?"
        if dbdc then
            vifs[j].__channel = mtkwifi.token_get(cfgs.Channel, j, 0)
            vifs[j].__wirelessmode = mtkwifi.token_get(cfgs.WirelessMode, j, 0)
        end

        vifs[j].__authmode = mtkwifi.token_get(cfgs.AuthMode, j, "OPEN")
        vifs[j].__encrypttype = mtkwifi.token_get(cfgs.EncrypType, j, "NONE")
        vifs[j].__hidessid = mtkwifi.token_get(cfgs.HideSSID, j, 0)
        vifs[j].__noforwarding = mtkwifi.token_get(cfgs.NoForwarding, j, 0)
        vifs[j].__wmmcapable = mtkwifi.token_get(cfgs.WmmCapable, j, 0)
        vifs[j].__txrate = mtkwifi.token_get(cfgs.TxRate, j, 0)
        vifs[j].__ieee8021x = mtkwifi.token_get(cfgs.IEEE8021X, j, 0)
        vifs[j].__preauth = mtkwifi.token_get(cfgs.PreAuth, j, 0)
        vifs[j].__rekeymethod = mtkwifi.token_get(cfgs.RekeyMethod, j, 0)
        vifs[j].__rekeyinterval = mtkwifi.token_get(cfgs.RekeyInterval, j, 0)
        vifs[j].__pmkcacheperiod = mtkwifi.token_get(cfgs.PMKCachePeriod, j, 0)
        vifs[j].__ht_extcha = mtkwifi.token_get(cfgs.HT_EXTCHA, j, 0)
        vifs[j].__radius_server = mtkwifi.token_get(cfgs.RADIUS_Server, j, 0)
        vifs[j].__radius_port = mtkwifi.token_get(cfgs.RADIUS_Port, j, 0)
        vifs[j].__wepkey_id = mtkwifi.token_get(cfgs.DefaultKeyID, j, 0)
        vifs[j].__wscconfmode = mtkwifi.token_get(cfgs.WscConfMode, j, 0)
        vifs[j].__wepkeys = {
            cfgs["Key"..j.."Str1"],
            cfgs["Key"..j.."Str2"],
            cfgs["Key"..j.."Str3"],
            cfgs["Key"..j.."Str4"],
        }
        vifs[j].__wpapsk = cfgs["WPAPSK"..j]

        -- VoW
        vifs[j].__atc_tp     = mtkwifi.token_get(cfgs.VOW_Rate_Ctrl_En,    j, 0)
        vifs[j].__atc_min_tp = mtkwifi.token_get(cfgs.VOW_Group_Min_Rate,  j, "")
        vifs[j].__atc_max_tp = mtkwifi.token_get(cfgs.VOW_Group_Max_Rate,  j, "")
        vifs[j].__atc_at     = mtkwifi.token_get(cfgs.VOW_Airtime_Ctrl_En, j, 0)
        vifs[j].__atc_min_at = mtkwifi.token_get(cfgs.VOW_Group_Min_Ratio, j, "")
        vifs[j].__atc_max_at = mtkwifi.token_get(cfgs.VOW_Group_Max_Ratio, j, "")

        -- TODO index by vifname
        vifs[vifs[j].vifname] = vifs[j]
    end

    return vifs
end

function mtkwifi.__setup_apcli(cfgs, devname, mainidx, subidx)
    local l1dat, l1 = mtkwifi.__get_l1dat()
    local dridx = l1dat and l1.DEV_RINDEX

    local apcli = {}
    local dev_idx = string.match(devname, "(%w+)")
    local apcli_prefix = l1dat and l1dat[dridx][devname].apcli_ifname or
                         dbdc_apcli_prefix[mainidx][subidx]

    local apcli_name = apcli_prefix.."0"

    if mtkwifi.exists("/sys/class/net/"..apcli_name) then
        apcli.vifname = apcli_name
         apcli.vifidx = "1"
        local iwapcli = mtkwifi.read_pipe("iwconfig "..apcli_name.." | grep ESSID 2>/dev/null")

        local _,_,ssid = string.find(iwapcli, "ESSID:\"(.*)\"")
        local flags = tonumber(mtkwifi.read_pipe("cat /sys/class/net/"..apcli_name.."/flags 2>/dev/null")) or 0
        apcli.state = flags%2 == 1 and "up" or "down"
        if not ssid or ssid == "" then
            apcli.status = "未连接"
        else
            apcli.ssid = ssid
            apcli.status = "已连接"
        end
        apcli.devname = apcli_name
        apcli.bssid = mtkwifi.read_pipe("cat /sys/class/net/"..apcli_name.."/address 2>/dev/null") or "?"
        local flags = tonumber(mtkwifi.read_pipe("cat /sys/class/net/"..apcli_name.."/flags 2>/dev/null")) or 0
        apcli.ifstatus = flags%2 == 1 and "up" or ""
        return apcli
    else
        return
    end
end

function mtkwifi.get_all_devs()
    local devs = {}
    local i = 1 -- dev idx
    local profiles = mtkwifi.search_dev_and_profile()
    local wpa_support = 0
    local wapi_support = 0

    for devname,profile in pairs(profiles) do
        mtkwifi.debug("debug", "checking "..profile)

        local fd = io.open(profile,"r")
        if not fd then
            mtkwifi.debug("debug", "cannot find "..profile)
        else
            fd:close()
            mtkwifi.debug("debug", "load "..profile)
            mtkwifi.debug("loading profile"..profile)
            local cfgs = mtkwifi.load_profile(profile)
            if not cfgs then
                mtkwifi.debug("error loading profile"..profile)
                mtkwifi.debug("err", "error loading "..profile)
                return
            end
            devs[i] = {}
            devs[i].vifs = {}
            devs[i].apcli = {}
            devs[i].devname = devname
            devs[i].profile = profile
            local tmp = ""
            tmp = string.split(devname, ".")
            devs[i].maindev = tmp[1]
            devs[i].mainidx = tonumber(tmp[2]) or 1
            devs[i].subdev = devname
            devs[i].subidx = string.match(tmp[3] or "", "(%d+)")=="2" and 2 or 1
            devs[i].devband = tonumber(tmp[3])
            if devs[i].devband then
                devs[i].multiprofile = true
                devs[i].dbdc = true
            end
            devs[i].version = mtkwifi.read_pipe("cat /etc/wireless/"..devs[i].maindev.."/version 2>/dev/null") or "unknown"
            devs[i].ApCliEnable = cfgs.ApCliEnable
            devs[i].WirelessMode = cfgs.WirelessMode
            devs[i].WirelessModeList = {}
            for key, value in pairs(DevicePropertyMap) do
                local found = string.find(string.upper(devname), string.upper(value.device))
                if found then
                    for k=1,#value.band do
                        devs[i].WirelessModeList[tonumber(value.band[k])] = WirelessModeList[tonumber(value.band[k])]
                    end
                end
            end
            devs[i].WscConfMode = cfgs.WscConfMode
            devs[i].AuthModeList = AuthModeList
            devs[i].WpsEnableAuthModeList = WpsEnableAuthModeList

            if wpa_support == 1 then
                table.insert(devs[i].AuthModeList,"WPAPSK")
                table.insert(devs[i].AuthModeList,"WPA")
            end

            if wapi_support == 1 then
                table.insert(devs[i].AuthModeList,"WAIPSK")
                table.insert(devs[i].AuthModeList,"WAICERT")
            end
            devs[i].ApCliAuthModeList = ApCliAuthModeList
            devs[i].WPA_Enc_List = WPA_Enc_List
            devs[i].WEP_Enc_List = WEP_Enc_List
            devs[i].Channel = tonumber(cfgs.Channel)
            devs[i].DBDC_MODE = tonumber(cfgs.DBDC_MODE)
            devs[i].band = devs[i].devband or mtkwifi.band(cfgs.WirelessMode)

            if cfgs.MUTxRxEnable then
                if tonumber(cfgs.ETxBfEnCond)==1
                    and tonumber(cfgs.MUTxRxEnable)==0
                    and tonumber(cfgs.ITxBfEn)==0
                    then devs[i].__mimo = 0
                elseif tonumber(cfgs.ETxBfEnCond)==0
                    and tonumber(cfgs.MUTxRxEnable)==0
                    and tonumber(cfgs.ITxBfEn)==1
                    then devs[i].__mimo = 1
                elseif tonumber(cfgs.ETxBfEnCond)==1
                    and tonumber(cfgs.MUTxRxEnable)==0
                    and tonumber(cfgs.ITxBfEn)==1
                    then devs[i].__mimo = 2
                elseif tonumber(cfgs.ETxBfEnCond)==1
                    and tonumber(cfgs.MUTxRxEnable)>0
                    and tonumber(cfgs.ITxBfEn)==0
                    then devs[i].__mimo = 3
                elseif tonumber(cfgs.ETxBfEnCond)==1
                    and tonumber(cfgs.MUTxRxEnable)>0
                    and tonumber(cfgs.ITxBfEn)==1
                    then devs[i].__mimo = 4
                else devs[i].__mimo = 5
                end
            end

            if cfgs.HT_BW == "0" or not cfgs.HT_BW then
                devs[i].__bw = "20"
            elseif cfgs.HT_BW == "1" and cfgs.VHT_BW == "0" or not cfgs.VHT_BW then
                if cfgs.HT_BSSCoexistence == "0" or not cfgs.HT_BSSCoexistence then
                    devs[i].__bw = "40"
                else
                    devs[i].__bw = "60" -- 20/40 coexist
                end
            elseif cfgs.HT_BW == "1" and cfgs.VHT_BW == "1" then
                devs[i].__bw = "80"
            elseif cfgs.HT_BW == "1" and cfgs.VHT_BW == "2" then
                devs[i].__bw = "160"
            elseif cfgs.HT_BW == "1" and cfgs.VHT_BW == "3" then
                devs[i].__bw = "161"
            end

            devs[i].vifs = mtkwifi.__setup_vifs(cfgs, devname, devs[i].mainidx, devs[i].subidx)
            devs[i].apcli = mtkwifi.__setup_apcli(cfgs, devname, devs[i].mainidx, devs[i].subidx)

            -- Setup reverse indices by devname
            devs[devname] = devs[i]

            if devs[i].apcli then
                devs[i][devs[i].apcli.devname] = devs[i].apcli
            end

            i = i + 1
        end
    end
    return devs
end

function mtkwifi.exists(path)
    local fp = io.open(path, "rb")
    if fp then fp:close() end
    return fp ~= nil
end

function mtkwifi.parse_mac(str)
    local macs = {}
    local pat = "^[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]$"

    local function ismac(str)
        if str:match(pat) then return str end
    end

    if not str then return macs end
    local t = str:split("\n")
    for _,v in pairs(t) do
            local mac = ismac(mtkwifi.__trim(v))
            if mac then
                table.insert(macs, mac)
            end
    end

    return macs
    -- body
end


function mtkwifi.scan_ap(vifname)
    os.execute("ifconfig "..vifname.." down")
    os.execute("ifconfig "..vifname.." up")
    os.execute("iwpriv "..vifname.." set SiteSurvey=0")
    os.execute("sleep 4") -- depends on your env
    local scan_result = mtkwifi.read_pipe("iwpriv "..vifname.." get_site_survey 2>/dev/null")

    local aplist = {}
    local xx = {}
    for i, line in ipairs(mtkwifi.__lines(scan_result)) do
        if #line>40 and string.match(line, " BSSID ") then
            xx.Ch = {string.find(line, "Ch "),3}
            xx.SSID = {string.find(line, "SSID "),32}
            xx.BSSID = {string.find(line, "BSSID "),17}
            xx.Security = {string.find(line, "Security "),22}
            xx.Signal = {string.find(line, "Sig%a%al"),4}
            xx.Mode = {string.find(line, "W-Mode"),5}
            xx.ExtCh = {string.find(line, "ExtCH"),6}
            xx.NT = {string.find(line, "NT"),2}
        end

        local tmp = {}
        if #line>40 and not string.match(line, " BSSID ") then
            tmp = {}
            tmp.channel = mtkwifi.__trim(string.sub(line, xx.Ch[1], xx.Ch[1]+xx.Ch[2]))
            tmp.ssid = mtkwifi.__trim(string.sub(line, xx.SSID[1], xx.SSID[1]+xx.SSID[2]))
            tmp.bssid = string.upper(mtkwifi.__trim(string.sub(line, xx.BSSID[1], xx.BSSID[1]+xx.BSSID[2])))
            tmp.security = mtkwifi.__trim(string.sub(line, xx.Security[1], xx.Security[1]+xx.Security[2]))
            tmp.authmode = mtkwifi.__trim(string.split(tmp.security, "/")[1])
            tmp.encrypttype = mtkwifi.__trim(string.split(tmp.security, "/")[2] or "NONE")
            tmp.rssi = mtkwifi.__trim(string.sub(line, xx.Signal[1], xx.Signal[1]+xx.Signal[2]))
            tmp.extch = mtkwifi.__trim(string.sub(line, xx.ExtCh[1], xx.ExtCh[1]+xx.ExtCh[2]))
            tmp.mode = mtkwifi.__trim(string.sub(line, xx.Mode[1], xx.Mode[1]+xx.Mode[2]))
            tmp.nt = mtkwifi.__trim(string.sub(line, xx.NT[1], xx.NT[1]+xx.NT[2]))
            table.insert(aplist, tmp)
        end
    end

    return aplist
end

function mtkwifi.__any_wsc_enabled(wsc_conf_mode)
    if (wsc_conf_mode == "") then
        return 0;
    end
    if (wsc_conf_mode == "7") then
        return 1;
    end
    if (wsc_conf_mode == "4") then
        return 1;
    end
    if (wsc_conf_mode == "2") then
        return 1;
    end
    if (wsc_conf_mode == "1") then
        return 1;
    end
    return 0;
end

function mtkwifi.__restart_if_wps(devname, ifname, cfgs)
    local devs = mtkwifi.get_all_devs()
    local ssid_index = devs[devname]["vifs"][ifname].vifidx
    local wsc_conf_mode = ""

    wsc_conf_mode=mtkwifi.token_get(cfgs["WscConfMode"], ssid_index, "")

    os.execute("iwpriv "..ifname.." set WscConfMode=0")
    mtkwifi.debug("iwpriv "..ifname.." set WscConfMode=0")
    os.execute("route delete 239.255.255.250")
    mtkwifi.debug("route delete 239.255.255.250")
    if(mtkwifi.__any_wsc_enabled(wsc_conf_mode)) then
        os.execute("iwpriv "..ifname.." set WscConfMode=7")
        mtkwifi.debug("iwpriv "..ifname.." set WscConfMode=7")
        os.execute("route add -host 239.255.255.250 dev br0")
        mtkwifi.debug("route add -host 239.255.255.250 dev br0")
    end

    -- execute wps_action.lua file to send signal for current interface
    os.execute("lua wps_action.lua "..ifname)
    mtkwifi.debug("lua wps_action.lua "..ifname)
    return cfgs
end

function mtkwifi.restart_8021x(devname, devices)
    local l1dat, l1 = mtkwifi.__get_l1dat()
    local dridx = l1dat and l1.DEV_RINDEX

    local devs = devices or mtkwifi.get_all_devs()
    local dev = devs[devname]
    local main_ifname = l1dat and l1dat[dridx][devname].main_ifname or dbdc_prefix[mainidx][subidx].."0"
    local prefix = l1dat and l1dat[dridx][devname].ext_ifname or dbdc_prefix[mainidx][subidx]

    local ps_cmd = "ps | grep -v grep | grep rt2860apd | grep "..main_ifname.." | awk '{print $1}'"
    local pid_cmd = "cat /var/run/rt2860apd_"..devs[devname].vifs[1].vifname..".pid"
    local apd_pid = mtkwifi.read_pipe(pid_cmd) or mtkwifi.read_pipe(ps_cmd)
    if tonumber(apd_pid) then
        os.execute("kill "..apd_pid)
    end

    local cfgs = mtkwifi.load_profile(devs[devname].profile)
    local auth_mode = cfgs['AuthMode']
    local ieee8021x = cfgs['IEEE8021X']
    local pat_auth_mode = {"WPA$", "WPA;", "WPA2$", "WPA2;", "WPA1WPA2$", "WPA1WPA2;"}
    local pat_ieee8021x = {"1$", "1;"}
    local apd_en = false

    for _, pat in ipairs(pat_auth_mode) do
        if string.find(auth_mode, pat) then
            apd_en = true
        end
    end

    for _, pat in ipairs(pat_ieee8021x) do
        if string.find(ieee8021x, pat) then
            apd_en = true
        end
    end

    if not apd_en then
        return
    end

    os.execute("rt2860apd -i "..main_ifname.." -p "..prefix)
end

return mtkwifi
