#!/usr/bin/lua

local px     = require "posix"
local uci    = require 'luci.model.uci'
local util   = require 'luci.util'
local io     = require 'io'
local socket = require 'socket'
local json   = require 'json'
local fs     = require "nixio.fs"
local ubus   = require "ubus"

local g_ubus
local g_cursor
local g_wan_busy_threshold = 100
local g_wan_busy_zero_num  = 20
local g_speedtest_interval = 24*60*60
local g_current_date       = 0
local g_current_day_trynum = 0
local g_current_day_num    = 0
local g_nextmins_speedtest = 0
local g_lasttime_speedtest = 0
local g_ubus_wanup_msg     = 1
local g_max_log_num        = 0
local g_current_log_num    = 0
local MAX_TRYNUM_PERDAY    = 1
local MAX_NUM_PERDAY       = 1
local MAX_BAND_ERROR       = 0.7
local g_user_band_up       = 0
local g_user_band_down     = 0
local g_begin_time    = "02:00"
local g_end_time      = "06:00"
local g_debug_file_id = nil
local g_debug_file    = false
local g_debug         = false
local g_need_test_again  = false
local TEST_AGAIN_TICKS   = 180

local PREPARE_SPEEDTEST_XML_INTERVEL = 30
local g_prepare_speedtest_xml_cnter  = 0
local g_prepare_speedtest_xml_last_try = 0
local g_prepare_speedtest_xml_trycnt = 0

local g_test_again_ticks = TEST_AGAIN_TICKS

local cfg = {
	['host']          = "127.0.0.1",
	['port']          = "1036",
	['cmd_nettb']     = "/usr/sbin/nettb",
	['cmd_upload']    = "/usr/bin/upload_speedtest",
	['cmd_download']  = "/usr/bin/download_speedtest",
	['cmd_speedtesturls']  = "/usr/bin/prepare_speedtest_xml",
	['cmd_miqos_set'] = "/etc/init.d/miqos change_band ",
	['cmd_miqos_get'] = "/etc/init.d/miqos show_band ",
	['tmp_log']       = "/tmp/speedtest.log",
	['cfg_file']      = "/etc/config/ab",
	['tmp_cfg_dir']   = "/tmp/etc/config/",
	['tmp_cfg_file']  = "/tmp/etc/config/ab",
        ['tmp_speedtest_xml'] = "/tmp/speedtest_urls.xml"
}

local SPEEDTEST_VER='STD'
if #arg >= 1 then
    if arg[1] == 'oversea' then
        SPEEDTEST_VER='OVERSEA'
    end
end

function time_to_mins(stime)
    local _,_,hour = string.find(stime, '([0-9]+):')
    local _,_,min  = string.find(stime, ':([0-9]+)')

    return (60*tonumber(hour)+tonumber(min))
end

function mins_to_time(mins)
    local hour,min = 0,0
    min  = mins%60
    hour = (mins-min)/60

    return string.format("%2d:%2d", hour, min)
end

function log_to_file(fileid, msg)
    local file_size = fileid:seek("end")

    if file_size > 1024*1024 then
	return
    end
    fileid:write(os.date("%x %X").." : "..msg.."\n")
end

--logger
--[[1 Alert, 3 Error 7 Debug ]]--
px.openlog("speedtest","np",LOG_USER)
function logger(loglevel,msg)
    if loglevel == 10 then
	if g_debug then
            px.syslog(3,msg)
	    if g_debug_file and g_debug_file_id then
		log_to_file(g_debug_file_id, msg)
	    end
	end
    else
        px.syslog(loglevel,msg)
    end
end

function execl2(command)
    local pp   = io.popen(command)
    local line = ""
    local data = {}

    while true do
        line = pp:read()
        if line == nil then
            break
        end
        data[#data+1] = line
    end
    pp:close()
    return data
end

-- 读取cfg到tmp的meory文件夹中
function cfg2tmp()
    local r1,r2,r3 = fs.mkdirr(cfg.tmp_cfg_dir)
    if not r1 then
        logger(3, 'fatal error: mkdir failed, code:' .. r2 .. ',msg:'..r3)
        return nil
    end

    r1,r2,r3 = fs.copy(cfg.cfg_file, cfg.tmp_cfg_file)
    if not r1 then
        logger(3,'fatal error: copy cfg file 2 /tmp memory failed. code:' .. r2 .. ',msg:'..r3)
        return nil
    end
    return true
end

-- 拷贝最新配置到memory中
function tmp2cfg()
    if not fs.copy(cfg.tmp_cfg_file, cfg.cfg_file) then
        logger(3,'fatal error: copy /tmp cfg file 2 /etc/config/ failed. exit.')
        return nil
    end
    return true
end

function copytab(st)
    local tab={}
    for k,v in pairs(st or {}) do
        if type(v) ~= 'table' then tab[k]=v
        else tab[k]=copytab(v) end
    end
    return tab
end

function get_conf_std(conf,type,opt,default)
    local x=uci.cursor()
    local s,e = pcall(function() return x:get(conf,type,opt) end)
    return e or default
end

-- execute command without anyoutput
function exec_cmd(tblist, ignore_error)
    local status = 0
    for _,v in pairs(tblist) do
        local cmd = v

        if g_debug then
            logger(3, '++' .. cmd)
            cmd = cmd .. ' >/dev/null 2>>' .. cfg.tmp_log
        else
            cmd = cmd .. " &>/dev/null "
        end

        if os.execute(cmd) ~= 0 and ignore_error ~= 1 then
            if g_debug then
                os.execute('echo "^^^ '.. cmd .. ' ^^^ " >>' .. cfg.tmp_log)
            end
            logger(3, '[ERROR]:  ' .. cmd .. ' failed!')
            dump_qdisc()
            return false
        end
    end

    return true
end

--split string with chars '$p'
string.split = function(s, p)
    local rt= {}
    string.gsub(s, '[^'..p..']+', function(w) table.insert(rt, w) end )
    return rt
end

function system_exit()
    logger(3,'======== Process Exit. =====')

    if g_debug_file_id then
	g_debug_file_id:flush()
	g_debug_file_id:close()
    end
    os.exit()
end

function system_init()
    if g_debug then
        os.execute("echo auto speedtest starting..... >>" .. cfg.tmp_log)
    end

    if g_debug and g_debug_file then
      g_debug_file_id = io.open(cfg.tmp_log, "a")
    end

    -- 将配置文件copy到tmp内存中,并初始化cursor
--[[    if not cfg2tmp() then
        return false
    end
--]]

    g_cursor = uci.cursor()
    if not g_cursor then
        logger(3,'set uci cursor failed. exit.')
        return false
    end

    if is_user_band_manual() then
        g_user_band_up   = 1
        g_user_band_down = 1
        logger(10,'user set band manual!')
    else
        logger(10,'user didnt set band manual!')
    end
--[[
    if not g_cursor:set_confdir(cfg.tmp_cfg_dir) then
        logger(3,'set tmp config dir failed. exit.')
        return false
    end
--]]
    g_ubus = ubus.connect()
    if not g_ubus then
        logger(3, 'failed to connect to ubusd!')
        return false
    end

    logger(10, "************ auto speedtest startup **************")
    g_current_date = tonumber(os.date("%d"))
    g_nextmins_speedtest = get_nexttime_speedtest()

    local lasttime = g_cursor:get("ab","settings","last_speedtest_time")
    g_lasttime_speedtest =  tonumber(lasttime)

    local maxlog   = g_cursor:get("ab","settings","max_log_num")
    g_max_log_num  =  tonumber(maxlog)

    local curntlog = g_cursor:get("ab","settings","current_log_num")
    g_current_log_num =  tonumber(curntlog)

    if g_current_log_num > g_max_log_num then
	g_current_log_num = 1
    end

    logger(10, "init get lasttime: "..lasttime.." lastdate: "..os.date("%x %X", g_lasttime_speedtest))
    logger(10, "max log num: "..g_max_log_num.." current log num: "..g_current_log_num)

-- SIGTERM to clear and exit
    px.signal(px.SIGTERM,
        function ()
            logger(3,'signal TERM to stop auto speed test.')
            system_exit()
        end)

    px.signal(px.SIGINT,
        function ()
            logger(3,'signal INT to stop auto speed test.')
            system_exit()
        end)

    return true
end


function get_list_wan_rate()
    local ret = g_ubus:call("trafficd", "list_wan_rate", {})

    if ret and ret.rate then
	return ret.rate
    end

    return nil;
end

function get_lasttime_speedtest()
    return g_lasttime_speedtest
end

function set_lasttime_speedtest()
    g_lasttime_speedtest = os.time()

    local date = string.format("%s", os.date("%x %X", g_lasttime_speedtest))

    g_cursor:set("ab","settings","last_speedtest_date",date)
    g_cursor:set("ab","settings","last_speedtest_time",tostring(g_lasttime_speedtest))
    g_cursor:commit('ab')
    logger(10, "set lasttime: "..g_lasttime_speedtest.." lastdate: "..os.date("%x %X", g_lasttime_speedtest))
end

-- choose a ramdom time between g_begin_time and g_end_time,
-- in case all routers start speedtest at the same moment
function get_nexttime_speedtest()
    local start_mins = time_to_mins(g_begin_time)
    local end_mins   = time_to_mins(g_end_time)

    math.randomseed(os.time())
    local next_mins = math.random(start_mins, end_mins)

    logger(10, "start mins:  "..g_begin_time.." "..time_to_mins(g_begin_time))
    logger(10, "end   mins:  "..g_end_time.." "..time_to_mins(g_end_time))
    logger(10, "next  mins:  "..mins_to_time(next_mins).." "..next_mins)

    return next_mins
end

--if speed test failed this time, we choose another time between now time and end time
function get_nexttime_case_fail()
    local start_mins = time_to_mins(g_begin_time)
    local end_mins   = time_to_mins(g_end_time)

    local date = os.date("%X")
    local _,_,hour = string.find(date, '^([0-9]+):')
    local _,_,min  = string.find(date, '^[0-9]+:([0-9]+):')
    local now_mins = 60*hour + min

    if (now_mins > start_mins) and (now_mins < end_mins) then
	start_mins = now_mins
    end

    math.randomseed(os.time())
    local next_mins = math.random(start_mins, end_mins)

    logger(10, "start mins:  "..g_begin_time.." "..time_to_mins(g_begin_time))
    logger(10, "end   mins:  "..g_end_time.." "..time_to_mins(g_end_time))
    logger(10, "now   mins:  "..mins_to_time(now_mins).." "..now_mins)
    logger(10, "next  mins:  "..mins_to_time(next_mins).." "..next_mins)

    return next_mins
end

function uci_commit_save(flag)
    if flag then
        g_cursor:commit('ab')

        -- tmp下的配置改变,复写回/etc下
--[[        if not tmp2cfg() then
            logger(1, 'copy tmp cfg to /etc/config/ failed.')
        end
--]]
    end
end

function get_uptime()
    local upt=io.open('/proc/uptime')
    local n=upt:read('*n')
    upt:close()
    return math.ceil(n)
end

function newset()
    local reverse = {}
    local set = {}
    return setmetatable(set, {__index = {
        insert = function(set, value)
            if not reverse[value] then
                table.insert(set, value)
                reverse[value] = table.getn(set)
            end
        end,
        remove = function(set, value)
            local index = reverse[value]
            if index then
                reverse[value] = nil
                local top = table.remove(set)
                if top ~= value then
                    reverse[top] = index
                    set[index] = top
                end
            end
        end
    }})
end

function is_user_band_manual()
    local manual = g_cursor:get("xiaoqiang","common","MANUAL")
    if manual then
	local ucfg = tonumber(manual)
	if ucfg and ucfg == 1 then
	    return true
	end
    end
    return false
end


function is_lasttest_long_enough()
    local last_time = get_lasttime_speedtest()
    local now_time = os.time()
    local year2000 = os.time{year=2000, month=1,day=1,hour=0}

    if now_time < year2000 then
        logger(10, "NTP is not ready now time: "..os.date("%x %X",now_time))
        return false
    end

    if last_time == 0 then
        logger(10, "Long enough now time: "..os.date("%x %X",now_time).." last time: "..os.date("%x %X",last_time))
	return true
    end

    if (last_time+g_speedtest_interval) < now_time then
        logger(10, "Long enough now time: "..os.date("%x %X",now_time).." last time: "..os.date("%x %X",last_time))
	return true
    end

    logger(10, "Not long enough now time: "..os.date("%x %X",now_time).." last time: "..os.date("%x %X",last_time))
    return false
end

function is_wan_busy()
    local sum,avg,max,zeronum,min = 0,0,0,0,9999999
    local listrate = get_list_wan_rate()

    if listrate and #listrate ~= 0 then
        for k,v in ipairs(listrate) do
            if v > max then
                max = v
            end

            if v < min then
		min = v
	    end

            if v==0 then
	        zeronum = zeronum+1
            end
            sum = sum + v
            logger(10, k..": "..v)
        end

	avg =  sum/#listrate
        logger(10, "is_wan_busy:  avg: "..avg.." max: "..max.." min: "..min.." zeronum: "..zeronum)

	if (avg < g_wan_busy_threshold) and (zeronum >= g_wan_busy_zero_num)then
	    return false
	else
	    logger(3, "stat_points_privacy network_speedtest=2|wanbusy|"..os.date("%x %X").."|"..avg.."|"..max.."|"..min.."|"..zeronum)
	    logger(10, "dotdata:  network_speedtest=2|wanbusy|"..os.date("%x %X").."|"..avg.."|"..max.."|"..min.."|"..zeronum)
	    return true
	end
    end

    return true
end

function is_just_power_up()
    local start_time = get_uptime()

    if start_time < 160 then
	return true
    end
    return false
end

function is_time_to_speedtest()
    local date = os.date("%X")
    local _,_,hour = string.find(date, '^([0-9]+):')
    local _,_,min  = string.find(date, '^[0-9]+:([0-9]+):')
    local now_mins = 60*tonumber(hour) + tonumber(min)

    if (now_mins >= g_nextmins_speedtest) and (now_mins <= time_to_mins(g_end_time)) then
	return true
    end

    logger(10, "Not time for speedtest  now: "..date.." begin: "..g_begin_time.." end: "..g_end_time.." next: "..mins_to_time(g_nextmins_speedtest))
    return false
end

function is_internet_connected()
    local cmd = cfg.cmd_nettb
    local res = execl2(cmd)

    if next(res)~=nil then
        for k,v in pairs(res) do
            logger(10, "got nettb info: "..v)
        end
	return false
    else
        return true
    end
end

function is_band_result_valid(new_up, new_down, old_up, old_down, flag)

    if flag == 3 then
        return true
    end

    if (new_up > 32) and (new_down > 128) then

        logger(10, "band result valid!")
        logger(10, "old_up: "..old_up.." old_down: "..old_down)
        logger(10, "new_up: "..new_up.." new_down: "..new_down)
        return true
    end

    logger(10, "band result invalid!")
    logger(10, "old_up: "..old_up.." old_down: "..old_down)
    logger(10, "new_up: "..new_up.." new_down: "..new_down)
    logger(3, "stat_points_privacy network_speedtest="..flag.."|banderr|"..os.date("%x %X").."|"..old_up.."|"..old_down.."|"..new_up.."|"..new_down)
    logger(10, "dotdata: network_speedtest="..flag.."|banderr|"..os.date("%x %X").."|"..old_up.."|"..old_down.."|"..new_up.."|"..new_down)
    return false
end

-- if band result is unacceptable, we do speed test again after 1 hour
function is_band_result_acceptable(new_up, new_down, old_up, old_down, flag)

    if flag == 3 then
        return true
    end

    if (new_up > 0.5*old_up) and (new_down > 0.5*old_down) then

        logger(10, "band result acceptable!")
        logger(10, "old_up: "..old_up.." old_down: "..old_down)
        logger(10, "new_up: "..new_up.." new_down: "..new_down)
        return true
    end

    logger(10, "band result unacceptable!")
    logger(10, "old_up: "..old_up.." old_down: "..old_down)
    logger(10, "new_up: "..new_up.." new_down: "..new_down)
    logger(3, "stat_points_privacy network_speedtest="..flag.."|banderr|"..os.date("%x %X").."|"..old_up.."|"..old_down.."|"..new_up.."|"..new_down)
    logger(10, "dotdata: network_speedtest="..flag.."|banderr|"..os.date("%x %X").."|"..old_up.."|"..old_down.."|"..new_up.."|"..new_down)
    return false
end

function get_miqosband()
    local upband, downband = 0, 0
    local cmd = cfg.cmd_miqos_get
    local res = execl2(cmd)

    logger(10, cmd)
    if res and next(res)~=nil then
	for k,v in ipairs(res) do
	    local _,_,tmp  = string.find(v, 'status=([0-9]+)')
	    local _,_,up   = string.find(v, 'uplink=([0-9]+%.?[0-9]+)')
	    local _,_,down = string.find(v, 'downlink=([0-9]+%.?[0-9]+)')

	    if tmp then
		status = tonumber(tmp)
	    end

	    if up then
		upband = tonumber(up)
	    end

	    if down then
		downband = tonumber(down)
	    end
	end

	if status == 0 then
	    --logger(10, "Get miqos band up: "..upband.." down: "..downband)
            return upband, downband
	end
    end

    --logger(10, "Get miqos band up: 0 down: 0")
    return 0, 0
end

function set_miqosband(up_band, down_band, flag)
    local cmd = cfg.cmd_miqos_set.." "..up_band.." "..down_band
    local res = execl2(cmd)
    local status = 1

    logger(10, cmd)
    if res and next(res)~=nil then
	for k,v in ipairs(res) do
	    local _,_,tmp = string.find(v, 'status=([0-9]+)')
	    if tmp then
		status = tonumber(tmp)
	    end
	end

	if status == 0 then
	    logger(10, "set miqos band succeed!")

	    local info = os.date("%x %X: ").."flag: "..flag.." up: "..up_band.." down: "..down_band
	    if g_current_log_num > g_max_log_num then
		g_current_log_num = 1
	    end
	    g_cursor:set("ab","settings","log"..g_current_log_num, info)
            g_current_log_num = g_current_log_num + 1
	    g_cursor:set("ab","settings","current_log_num", g_current_log_num)
	    g_cursor:commit('ab')

            set_lasttime_speedtest()
            if not is_user_band_manual() then
		local tmpup   = string.format("%.2f", up_band/1024)
		local tmpdown = string.format("%.2f", down_band/1024)
	        g_cursor:set("xiaoqiang","common","BANDWIDTH2", tmpup)
	        g_cursor:set("xiaoqiang","common","BANDWIDTH", tmpdown)
	        g_cursor:commit('xiaoqiang')
		logger(10, "set xiaoqiang band up: "..tmpup.." down: "..tmpdown)
            else
		logger(10, "user band manual,dont set xiaoqiang config")
            end

            return true
	end
    end
    return false
end

function do_speed_test_upload()
    local cmd = cfg.cmd_upload
    local res = execl2(cmd)
    local band = 0

    if next(res)~=nil then
        for k,v in pairs(res) do
	    local _,_,tmp = string.find(v, 'tx:([0-9]+%.[0-9]+)')
	    if tmp then
		band = tonumber(tmp) or 0
	    end
            --logger(10, "got upload info: "..v)
        end
	--logger(10,"got up band: "..band)
	return band
    else
        return 0
    end
end

function do_speed_test_download()
    local cmd = cfg.cmd_download
    local res = execl2(cmd)
    local band = 0

    if next(res)~=nil then
        for k,v in pairs(res) do
	    local _,_,tmp = string.find(v, 'rx:([0-9]+%.[0-9]+)')
	    if tmp then
		band = tonumber(tmp) or 0
	    end
            --logger(10, "got download info: "..v)
        end
	--logger(10,"got down band: "..band)
	return band
    else
        return 0
    end
end

--flag: 1.speedtest when power on  2.speedtest when time up 3.speedtest on cmd
--      4.speedtest again in 1 hour if last band result unacceptable
--return 1 means failed, return 0 means succeed
function do_speed_test(flag)
    local upband,downband = 0,0

    logger(10, "do speed test!")

    local miqos_upband,miqos_downband = get_miqosband()

    if flag ~= 3 then
        if miqos_upband==0 or miqos_downband==0 then
        --    logger(3, "stat_points_privacy network_speedtest="..flag.."|miqosinvalid|"..os.date("%x %X").."|"..miqos_upband.."|"..miqos_downband.."|0|0")
            logger(10, "network_speedtest="..flag.."|miqosinvalid|"..os.date("%x %X").."|"..miqos_upband.."|"..miqos_downband.."|0|0")
        --    return 1
        end
    end

    os.execute("/etc/init.d/miqos stop")
    downband = do_speed_test_download()
    upband   = do_speed_test_upload()
    os.execute("/etc/init.d/miqos start")

    logger(10, "test  upband: "..upband.." test   downband: "..downband)
    logger(10, "miqos upband: "..miqos_upband.." miqos  downband: "..miqos_downband)

    if upband==0 or downband==0 then
	logger(10, "speedtest or miqos band invalid!")
	if flag ~= 3 then
            logger(3, "stat_points_privacy network_speedtest="..flag.."|bandinvalid|"..os.date("%x %X").."|"..miqos_upband.."|"..miqos_downband.."|"..upband.."|"..downband)
            logger(10, "dotdata: network_speedtest="..flag.."|bandinvalid|"..os.date("%x %X").."|"..miqos_upband.."|"..miqos_downband.."|"..upband.."|"..downband)
	end
	return 1
    else
        if is_band_result_valid(upband,downband,miqos_upband,miqos_downband,flag) then
	    if flag == 4 or flag == 3 then
	        local res =  set_miqosband(upband, downband, flag)
                if res then
                    logger(3, "stat_points_privacy network_speedtest="..flag.."|success|"..os.date("%x %X").."|"..miqos_upband.."|"..miqos_downband.."|"..upband.."|"..downband)
                    logger(10, "dotdata: network_speedtest="..flag.."|success|"..os.date("%x %X").."|"..miqos_upband.."|"..miqos_downband.."|"..upband.."|"..downband)
		    return 0
                else
		    if flag~= 3 then
                        logger(3, "stat_points_privacy network_speedtest="..flag.."|miqoserr1|"..os.date("%x %X").."|"..miqos_upband.."|"..miqos_downband.."|"..upband.."|"..downband)
                        logger(10, "dotdata: network_speedtest="..flag.."|miqoserr1|"..os.date("%x %X").."|"..miqos_upband.."|"..miqos_downband.."|"..upband.."|"..downband)
		    end
		    return 1
	        end
	    else
	        if is_band_result_acceptable(upband,downband,miqos_upband,miqos_downband,flag) then
	            local res =  set_miqosband(upband, downband, flag)
                    if res then
                        logger(3, "stat_points_privacy network_speedtest="..flag.."|success|"..os.date("%x %X").."|"..miqos_upband.."|"..miqos_downband.."|"..upband.."|"..downband)
                        logger(10, "dotdata: network_speedtest="..flag.."|success|"..os.date("%x %X").."|"..miqos_upband.."|"..miqos_downband.."|"..upband.."|"..downband)
		        return 0
                    else
                        logger(3, "stat_points_privacy network_speedtest="..flag.."|miqoserr1|"..os.date("%x %X").."|"..miqos_upband.."|"..miqos_downband.."|"..upband.."|"..downband)
                        logger(10, "dotdata: network_speedtest="..flag.."|miqoserr1|"..os.date("%x %X").."|"..miqos_upband.."|"..miqos_downband.."|"..upband.."|"..downband)
		        return 1
	            end
	        else
		    g_need_test_again  = true
		    g_test_again_ticks = TEST_AGAIN_TICKS
	            logger(10, "band result unacceptable, test again 1 hour later!")
	        end
	    end
	end
    end
    return 1
end

function speedtest_by_cmd()

    if (g_user_band_down ~= 0) or (g_user_band_up ~= 0) then
	logger(10, "speedtest cmd user band is not zero: "..g_user_band_up.." "..g_user_band_down..", do nothing!")
	return 1
    end

    if not is_internet_connected() then
        logger(10, "speedtest cmd Internet is not connected, do nothing!")
        return 2
    end

    local res = do_speed_test(3)
    return res
end


function try_speedtest()

    if (g_user_band_down ~= 0) or (g_user_band_up ~= 0) then
	logger(10, "user band is not zero: "..g_user_band_up.." "..g_user_band_down..", do nothing!")
	return
    end

    if not is_lasttest_long_enough() then
	return
    end

    if (g_current_day_num >= MAX_NUM_PERDAY) or (g_current_day_trynum >= MAX_TRYNUM_PERDAY)then
	logger(10, "Exceed max num perday! cur: "..g_current_day_num.."("..MAX_NUM_PERDAY..") trycur: "..g_current_day_trynum.."("..MAX_TRYNUM_PERDAY..")")
	return
    end

    -- during the just startup time, we dont detect if wan is busy
    if g_ubus_wanup_msg == 1 and is_just_power_up() then
        if not is_internet_connected() then
	    logger(10, "Wanup but Internet is not connected, do nothing!")
	    return
        end
	logger(10, "Got wan up msg, do speed test!")
        g_ubus_wanup_msg = 0
	do_speed_test(1)
	g_current_day_num = g_current_day_num +1
	return
    end

    if is_time_to_speedtest() then
        if not is_internet_connected() then
	    logger(10, "Internet is not connected, do nothing!")
	    logger(3, "stat_points_privacy network_speedtest=2|uninternet|"..os.date("%x %X"))
	    logger(10, "dotdata: network_speedtest=2|uninternet|"..os.date("%x %X"))
            g_current_day_trynum = g_current_day_trynum + 1
	    return
        end

        if is_wan_busy() then
            g_current_day_trynum = g_current_day_trynum + 1
            logger(10, "Wan is busy, do nothing! ")
            g_nextmins_speedtest = get_nexttime_speedtest()
        else
            logger(10, "Wan is free, do speed test!")
            do_speed_test(2)
            g_current_day_num = g_current_day_num +1
            g_nextmins_speedtest = get_nexttime_speedtest()
        end
    end
end

--if last band result unacceptable, we do speed test again in 1 hour
function try_speedtest_again()

    if (g_user_band_down ~= 0) or (g_user_band_up ~= 0) then
	logger(10, "user band is not zero: "..g_user_band_up.." "..g_user_band_down..", do nothing!")
	return
    end

    if true then
        if not is_internet_connected() then
	    logger(10, "Internet is not connected, do nothing!")
	    logger(3, "stat_points_privacy network_speedtest=2|uninternet|"..os.date("%x %X"))
	    logger(10, "dotdata: network_speedtest=2|uninternet|"..os.date("%x %X"))
	    return
        end

        if is_wan_busy() then
            logger(10, "Wan is busy, do nothing! ")
        else
            logger(10, "Wan is free, do speed test!")
            do_speed_test(4)
        end
    end
end

function prepare_speedtest_urls()
    local cmd = cfg.cmd_speedtesturls

    if g_prepare_speedtest_xml_trycnt > 3 then
        logger(10, "try num is enough do nothing: "..g_prepare_speedtest_xml_trycnt)
        return
    end

    g_prepare_speedtest_xml_cnter = g_prepare_speedtest_xml_cnter + 1
    if SPEEDTEST_VER ~= "OVERSEA" then
        logger(10, "prepare urls is not oerversea version !")
        return
    end

    filexml = io.open(cfg.tmp_speedtest_xml)
    if filexml then
        filexml:close()
        logger(10, "prepare urls already got url xml !")
        return
    end

    if not is_internet_connected() then
        logger(10, "prepare urls  Internet is not connected, do nothing!")
        return
    end

    if g_prepare_speedtest_xml_trycnt ~= 0 then
        if g_prepare_speedtest_xml_cnter - g_prepare_speedtest_xml_last_try < PREPARE_SPEEDTEST_XML_INTERVEL then
            logger(10, "its not time for getting xml cnter: "..g_prepare_speedtest_xml_cnter.." last: "..g_prepare_speedtest_xml_last_try)
            return
        end
    end

    g_prepare_speedtest_xml_trycnt = g_prepare_speedtest_xml_trycnt + 1
    g_prepare_speedtest_xml_last_try = g_prepare_speedtest_xml_cnter
    logger(3, "start getting speedtest url xml !")
    local res = execl2(cmd)
    filexml = io.open(cfg.tmp_speedtest_xml)
    if filexml then
        filexml:close()
        logger(3, "get speedtest url xml ok!")
    else
        logger(3, "get speedtest url xml failed!")
    end
end

-----------------------------------------------------------------------------
--  loop work for speed test
-----------------------------------------------------------------------------
function main_loop()

    local server = assert(socket.bind(cfg.host, cfg.port))
    server:settimeout(1)

    -- tables f
    local set=newset()
    set:insert(server)    -- add 'server' into select events

    local gc_timer=0
    while true do
	logger(10, "--------- main loop ---------")
        local readable, _, error = socket.select(set, nil , 20)
	gc_timer = gc_timer + 1
        if gc_timer >= 20 then
            gc_timer = 0
            local tmp_cnt = collectgarbage("count")
            logger(10, "LUA GC count: " .. tmp_cnt)
            tmp_cnt = collectgarbage("collect")
            logger(10, "LUA GC collect: " .. tmp_cnt)
        end

        for _,v in ipairs(readable) do

            if v == server then
                logger(3, 'new client come in ...')
                local clt=v:accept()
                if clt then
                    clt:settimeout(1)
                    set:insert(clt)
                else
                    logger(3, 'accept client error.')
                end
            else
                local data,error = v:receive()

                if error then
                    v:close()
                    logger(3, 'client is disconnected.')
                    set:remove(v)
                else
                    local args=string.split(data,' ')
                    if not args[1] then
                        v:send(json.encode({status=3}))
                    else
			logger(10, "Recv cmd: "..args[1])
                        if args[1] == 'status' then
                            logger(3,'======== COMMAND status============')
                            local stats = string.format("\ng_begin_time          : %s\n",g_begin_time)
                            stats = stats..string.format("g_end_time           : %s\n",g_end_time)
                            stats = stats..string.format("g_lasttime_speedtest : %s\n",os.date("%x %X", g_lasttime_speedtest))
                            stats = stats..string.format("g_nextmins_speedtest : %s\n",mins_to_time(g_nextmins_speedtest))
                            stats = stats..string.format("g_current_date       : %d\n",g_current_date)
                            stats = stats..string.format("g_current_day_trynum : %d\n",g_current_day_trynum)
                            stats = stats..string.format("g_current_day_num    : %d\n",g_current_day_num)
                            stats = stats..string.format("g_max_log_num        : %d\n",g_max_log_num)
                            stats = stats..string.format("g_current_log_num    : %d\n",g_current_log_num)
                            stats = stats..string.format("g_user_band_up       : %d\n",g_user_band_up)
                            stats = stats..string.format("g_user_band_down     : %d\n",g_user_band_down)

                            v:send(json.encode({status=2,data=stats}))
                        elseif args[1] == 'stop' then
                            logger(3,'======== COMMAND stop  ============')
                            v:send(json.encode({status=0}) .. "\n")
                            v:close()
                            system_exit()    -- exit system
                        elseif args[1] == 'wan_up' then
                            logger(3,'======== COMMAND wan_up============')
			    g_ubus_wanup_msg = 1
                            v:send(json.encode({status=0}) .. "\n")
                        elseif args[1] == 'debug_on' then
                            logger(3,'======== COMMAND debug_on============')
			    g_debug = true
                            v:send(json.encode({status=0}) .. "\n")
                        elseif args[1] == 'debug_off' then
                            logger(3,'======== COMMAND debug_off============')
			    g_debug = false
                            v:send(json.encode({status=0}) .. "\n")
                        elseif args[1] == 'do_speedtest' then
                            logger(3,'======== COMMAND do_speedtest============')
			    local res = speedtest_by_cmd()
                            v:send(json.encode({status=res}) .. "\n")
                        elseif args[1] == 'set_userband' then
                            logger(3,'======== COMMAND set_userband============')
			    g_user_band_up   = tonumber(args[2]) or 0
			    g_user_band_down = tonumber(args[3]) or 0
                            v:send(json.encode({status=0}) .. "\n")
                        elseif args[1] == 'next_time' then
                            logger(3,'======== COMMAND nexttime============')
			    g_nextmins_speedtest = time_to_mins(args[2])
                            v:send(json.encode({status=0, data=args[2].." "..g_nextmins_speedtest}) .. "\n")
                        else
                            v:send(json.encode({status=2,data='Not supported command.'}))
                        end
                    end

                    v:send('\n')
                    v:close()
                    set:remove(v)
                end
            end
        end

        prepare_speedtest_urls()
        local date = tonumber(os.date("%d"))
        if g_current_date ~= date then
            logger(10, "date change from "..g_current_date.." to "..date)
	    g_current_day_trynum = 0
	    g_current_day_num    = 0
	    g_current_date = date
        end

	if g_need_test_again then
	    if g_test_again_ticks > 1 then
		g_test_again_ticks = g_test_again_ticks - 1
                logger(10, "test again ticks left: "..g_test_again_ticks)
	    else
		g_need_test_again = false
		g_test_again_ticks = TEST_AGAIN_TICKS
                logger(10, "try speed test again! ")
		try_speedtest_again()
	    end

	end
	try_speedtest()
    end
end

function main()
    if system_init() then
        local s, e = pcall(function() main_loop() end)
        if not s
        then
            logger(3,e)
	    print("pcall exit!")
        end
    else
        print("system initial failed. exit.")
        logger(3, 'system initial failed. exit.')
    end
end

main()

