local Config = require("Config")
local pi = math.acos(-1)
local sqrt2 = math.sqrt(2)
local os	= _G.os
local math  = _G.math
local floor = math.floor
local cos	= math.cos
local sin	= math.sin
local atan2	= math.atan2

function getTableCount(s)
	local count = 0
	if s == nil then return count end
	for _ in pairs(s) do
		count = count + 1
	end
	return count
end

function copyTableSimple(s, t, noNewTB)
	for k, v in pairs(s) do
		if type(v) == "table" then
			if not noNewTB then
				t[k] = {}
			end
			copyTableSimple(v, t[k])
		else
			t[k] = v
		end
	end
end

function copyTable(s)
	if not s then return end		
	local t = {}
	copyTableSimple(s,t)
	return t
end

function initTable(tb)
	if not tb then return end	
	for k in pairs(tb) do
		tb[k]= nil
	end
end

function cleanTable(s)
	for k,v in pairs(s) do
		s[k] = nil
	end
end

function getBit(n, index)
	local div = 2 ^ index
	return (n - n % div) / div % 2
end

function setBit(n, index)
	local bit = getBit(n, index)
	if bit ~= 0 then
		assert()
	end
	return n + 2 ^ index
end

function hypot(a, b)
	return math.sqrt(a * a + b * b)
end

local out = {}
local out_len = 0
function printTable(tb, step)
	if not step then
		step = 0
		out_len = 0
	end
	for k, v in pairs(tb) do
		for i = 1, step do
			out_len = out_len + 1
			out[out_len] = "	"
		end

		if type(k) == "number" then
			out_len = out_len + 1
			out[out_len] = "["
		end

		out_len = out_len + 1
		out[out_len] = k

		if type(k) == "number" then
			out_len = out_len + 1
			out[out_len] = "]"
		end

		out_len = out_len + 1
		out[out_len] = "="

		if type(v) == "string" then
			out_len = out_len + 1
			out[out_len] = "\""
		end

		out_len = out_len + 1
		out[out_len] = type(v) == "table" and "" or v
		if type(v) == "boolean" then
			out[out_len] = tostring(v)
		end

		if type(v) == "string" then
			out_len = out_len + 1
			out[out_len] = "\""
		end

		out_len = out_len + 1
		out[out_len] = "\n"

		if type(v) == "table" then
			printTable(v, step + 1)
		end
	end
	if step == 0 then
		local str = table.concat(out, nil, 1, out_len)
        print(str)
		return str
	end
end


function serialize(val, name, skipmap, depth, buf)
    skipmap = skipmap or {}      -- 用来检测循环引用
    depth   = depth   or 0
    buf     = buf     or {}
    local indent = string.rep("  ", depth)

    local t = type(val)
    if t == "string" then
        table.insert(buf, string.format("%q", val))
    elseif t == "number" or t == "boolean" or t == "nil" then
        table.insert(buf, tostring(val))
    elseif t == "table" then
        -- 循环引用检测
        if skipmap[val] then
            table.insert(buf, skipmap[val])
            return buf
        end
        skipmap[val] = string.format("<%s>", name or "table")

        table.insert(buf, "{\n")
        local idx = 1
        for k, v in pairs(val) do
            -- 拼 key
            if type(k) == "string" and k:match("^[%a_][%w_]*$") then
                table.insert(buf, indent .. "  " .. k .. " = ")
            else
                table.insert(buf, indent .. "  [" )
                serialize(k, nil, skipmap, 0, buf)
                table.insert(buf, "] = ")
            end
            -- 拼 value
            serialize(v, nil, skipmap, depth + 1, buf)
            table.insert(buf, ",\n")
        end
        table.insert(buf, indent .. "}")
    elseif t == "function" then
        table.insert(buf, string.format("<function:%s>", tostring(val)))
    elseif t == "userdata" then
        table.insert(buf, string.format("<userdata:%s>", tostring(val)))
    elseif t == "thread" then
        table.insert(buf, string.format("<thread:%s>", tostring(val)))
    else
        table.insert(buf, "<??>")
    end
    return buf
end




-- 判断是否同一天
function isSameDay(time1)
	if not time1 then
		return
	end
	local now = os.time()
	local tToday = os.date("%Y%m%d", now)
	local t1 = os.date("%Y%m%d", time1)
	if tToday ~= t1 then
		return
	end
	return now
end

-- 判断传入的两个时间是否同一天
function isSameDayByTimes(time1,time2)
	if not time1 or not time2 then
		return
	end
	local t1 = os.date("%Y%m%d", time1)
	local t2 = os.date("%Y%m%d", time2)
	if t1 ~= t2 then
		return
	end
	return true
end

-- 是否同一个月
function isSameMonth(time)
	local now = os.date("*t")
	local d = os.date("*t",time)	
	return now.year==d.year and now.month==d.month
end

-- 判断传入时间点与当前时间相差多少天
local tb = {}
function diffDay(time)
	local d = os.date("*t",time)
	tb.year = d.year
	tb.month = d.month
	tb.day = d.day
	time = os.time(tb)
	local nowDay = os.date("*t")
	tb.year = nowDay.year
	tb.month = nowDay.month
	tb.day = nowDay.day
	local nowTime = os.time(tb)
	return math.ceil(math.abs(nowTime-time)/86400)
end

-- 判断传入时间点与当前时间相差多少小时
function diffHour(time)
	local d = os.date("*t",time)
	tb.year = d.year
	tb.month = d.month
	tb.day = d.day
	time = os.time(tb)
	local nowDay = os.date("*t")
	tb.year = nowDay.year
	tb.month = nowDay.month
	tb.day = nowDay.day
	local nowTime = os.time(tb)
	return math.ceil(math.abs(nowTime-time)/3600)
end

function diffDayByTimes(time1,time2)
	local t1 = os.date("*t",time1)
	tb.year = t1.year
	tb.month = t1.month
	tb.day = t1.day
	time1 = os.time(tb)
	local t2 = os.date("*t",time2)
	tb.year = t2.year
	tb.month = t2.month
	tb.day = t2.day
	time2 = os.time(tb)
	return math.ceil(math.abs(time1-time2)/86400)
end

-- 获取本月有多少天
function getMonthDayNum()
	local currMonthDate = os.date("*t")
	tb.year = currMonthDate.year
	tb.month = currMonthDate.month
	tb.day = currMonthDate.day
	currMonthDate.hour = nil 
	currMonthDate.min = nil
	currMonthDate.sec = nil
	currMonthDate.day = 1
	tb.day = 1
	
	if tb.month<12 then	
		tb.month = tb.month + 1
	else
		tb.year = tb.year+1 
		tb.month = 1
	end
	
	local nextMonthFirstDayTime = os.time(tb)
	local currMonthTime = os.time(currMonthDate)
	
	return math.floor(math.abs(nextMonthFirstDayTime-currMonthTime)/86400)
end

-- 取时间间隔 s
function getInterval(time)
	if not time then
		return math.huge
	end
	local now = os.time()
	return now - time
end

--参数结构：year=****,month=****,day=****
function isTime( startDate, endDate)
	local date_min = os.time(startDate) - 12 * 60 * 60
	local date_max = os.time(endDate) + 12 * 60 * 60
	local tmp_time = os.time()
	if not (date_min <= tmp_time and tmp_time < date_max) then
		return false
	end
	return true, date_max - tmp_time
end

-- check time
function checkTime(startTimeConf,endTimeConf)
	local nowDate = os.date("*t")
	local nowMin = nowDate.hour * 60 + nowDate.min
	
	local startMin = startTimeConf.hour * 60 + startTimeConf.min 
	local endMin = endTimeConf.hour * 60 + endTimeConf.min
	
	if nowMin >= startMin and nowMin < endMin then
		return true
	end 
end



-- 检查传入时间，是否经过resetTime时间点;
function isTimeOver(time,resetTime)
	
	if not time or not resetTime then
		return
	end
	
	local nowTime = os.time()
	local nowDay = os.date("%d", nowTime) + 0
	local nowHour = os.date("%H", nowTime) + 0
	
	local getDay = os.date("%d", time) + 0
	local getHour = os.date("%H", time) + 0
	
	local dirty = false
	if nowDay == getDay then
		if getHour < resetTime and nowHour >= resetTime then
			dirty = true
		end
	elseif nowDay == getDay + 1 then
		if nowHour >= resetTime then
			dirty = true	
		end
	else
		dirty = true
	end
	return dirty
end

tmpDate = tmpDate or {}
-- 取本月开始时间
function getMonthStartTime(time)
	local d = os.date("*t",time)
	tmpDate.year = d.year
	tmpDate.month = d.month
	tmpDate.day = 1
	tmpDate.hour = 0
	tmpDate.min = 0
	tmpDate.sec = 0
	return os.time(tmpDate)
end

-- 取本周开始时间 周一凌晨
function getWeekStartTime(time)
	local d = os.date("*t",time)
	tmpDate.year = d.year
	tmpDate.month = d.month
	tmpDate.hour = 0
	tmpDate.min = 0
	tmpDate.sec = 0
	local day = nil
	if d.wday == 1 then
		day = 6
	else
		day = d.wday - 2
	end
	tmpDate.day = d.day - day
	return os.time(tmpDate)
end

-- 取本日开始时间
function getDayStartTime(time)
	local d = os.date("*t",time)
	tmpDate.year = d.year
	tmpDate.month = d.month
	tmpDate.day = d.day
	tmpDate.hour = 0
	tmpDate.min = 0
	tmpDate.sec = 0
	return os.time(tmpDate)
end

-- 取相差几天时间
function getNextDayTime(time, day)
	local d = os.date("*t",time)
	tmpDate.year = d.year
	tmpDate.month = d.month
	tmpDate.day = d.day
	tmpDate.hour = 0
	tmpDate.min = 0
	tmpDate.sec = 0
	local ts = os.time(tmpDate)
	local dayTs = day * 86400
	return ts + dayTs
end

-- 获取前几天数据
function getLastDayTime(time, day)
	local d = os.date("*t",time)
	tmpDate.year = d.year
	tmpDate.month = d.month
	tmpDate.day = d.day
	tmpDate.hour = 0
	tmpDate.min = 0
	tmpDate.sec = 0
	local ts = os.time(tmpDate)
	local dayTs = day * 86400
	return ts - dayTs
end

-- 取本日特定小时时间戳
function getDayHourTime(time, hour)
	local d = os.date("*t",time)
	tmpDate.year = d.year
	tmpDate.month = d.month
	tmpDate.day = d.day
	tmpDate.hour = hour
	tmpDate.min = 0
	tmpDate.sec = 0
	return os.time(tmpDate)
end

-- 判断传入时间点，是否同周
function isSameWeek(time1,time2)
	if not time1 or not time2 then
		return
	end
	return getWeekStartTime(time1) == getWeekStartTime(time2)
end

-- 星期中第几天的数字表示 1（表示星期天）到 7（表示星期六） 
function getWeekDay( time )
	local time = time or os.time()
	local d = os.date("*t",time)
	return d.wday
end

-- 处于第几分钟
function getMin(time)
	time = time or os.time()
	local d = os.date("*t",time)
	return d.min
end


function split(str, split_char,isNumber)
    local t = {}
    while string.len(str)>0 do
        local pos = string.find(str, split_char);
        if (not pos) then
        	if isNumber then
            	t[#t + 1] = tonumber(str) or 0
            else
            	t[#t + 1] = str
            end
            break
        end
        local sub_str = string.sub(str, 1, pos - 1)
    	if isNumber then
        	t[#t + 1] = tonumber(sub_str) or 0
        else
        	t[#t + 1] = sub_str
        end
        str = string.sub(str, pos + 1, #str)
    end
    return t
end


TONUMBER_KEY   = "key"
TONUMBER_VALUE = "value"
TONUMBER_ALL   = "all"
--- 灵活解析键值字符串
-- @param str           源字符串，如 "1001-10|1002-11"
-- @param segSep        段分隔符，如 "|"（默认 "|"）
-- @param kvSep         键值分隔符，如 "-"（默认 "-"）
-- @param tonumberMode  可选："key"只转键，"value"只转值，"all"都转，nil不转
-- @return table        解析后的字典表
function parseKVString(str, segSep, kvSep, tonumberMode)
    -- 防护：nil 或空字符串直接返回空表
    if not str or str == "" then
        return {}
    end

    -- 去掉首尾空白（可选，视需求而定）
    str = string.match(str, "^%s*(.-)%s*$") or ""
    if str == "" then
        return {}
    end

    segSep = segSep or "|"
    kvSep = kvSep or "-"

    -- 转义模式中的特殊字符
    local function escapePattern(s)
        return (string.gsub(s, "([%-%.%+%*%?%[%]%^%$%(%)%%])", "%%%1"))
    end

    local segPattern = escapePattern(segSep)

    local result = {}

    for segment in string.gmatch(str, "([^" .. segPattern .. "]+)") do
        local pos = string.find(segment, kvSep, 1, true)
        if pos then
            local key = string.sub(segment, 1, pos - 1)
            local value = string.sub(segment, pos + string.len(kvSep))

            if tonumberMode == "key" or tonumberMode == "all" then
                key = tonumber(key) or key
            end
            if tonumberMode == "value" or tonumberMode == "all" then
                value = tonumber(value) or value
            end

            result[key] = value
        end
    end

    return result
end



-- 根据KEY从小到大排序
function pairsByKeys(t)
	local a = {}
	for n in pairs(t) do
		a[#a+1] = n
	end
	table.sort(a)
	local i = 0
	return function()
	i = i + 1
	return a[i], t[a[i]]
	end
end 

function cmptb(a, b)
	for k, v in pairs(a) do
		if type(v) == "table" then
			if type(b[k]) ~= "table" then
				return
			end
			if not cmptb(v, b[k]) then
				return
			end
		else
			if b[k] ~= v and k ~= "now" and k ~= "uid" then
				return
			end
		end
	end
	for k, v in pairs(b) do
		if type(v) == "table" then
			if type(a[k]) ~= "table" then
				return
			end
			if not cmptb(v, a[k]) then
				return
			end
		else
			if a[k] ~= v and k ~= "now" and k ~= "uid" then
				return
			end
		end
	end
	return true
end

function getWay(dx, dy)
	return ((floor(( atan2(dy, dx) + 11.388) / 1.57)) % 4) * 2;
end

function tableIsEmpty(tb)
	return _G.next(tb) == nil
end

-- 服务器是否符合
function checkSvrIndex(svrIndexs, svrIndex)
	if #svrIndexs < 1 then return true end

	if not svrIndex then
		svrIndex = Config.SVR_INDEX
	end

	for i = 1, #svrIndexs do
		if svrIndexs[i][1] <= svrIndex and 
			svrIndex <= svrIndexs[i][2] then
			return true
		end
	end
end

-- 含中文文本长度
function utf8len(content)
    local len = string.len(content)
    local left = len
    local cnt = 0
    local arr = {0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc}
    while left ~= 0 do
        local tmp = string.byte(content, -left)
        local i = #arr
        while arr[i] do
            if tmp >= arr[i] then
                left = left - i
                break
            end
            i = i - 1
        end
        cnt = cnt + 1
    end
    return cnt
end

function checkRandom(cmpValue)
	local r = math.random(0,10000)
	if r < cmpValue then
		return true
	end
end

SEPARATOR = "||"
function format(...)
	local str = ""
	local t = {}
	for k,v in ipairs({...}) do
		if k == 1 then
			str = str .. v
		else
			t = split(v, SEPARATOR)
			if #t > 1 then
				t[1] = string.gsub(t[1],"({(.-)})",function(p1,p2)
					p2 = tonumber(p2)
					if p2 and t[p2+1] then
						return string.sub(t[p2+1], 2)
					else
						return p1
					end
				end)
			end
            if t[1] then
			   str = str .. SEPARATOR .. t[1]
            end
		end
	end
	return str
end

--------------------------------------------------------
--- 新增table.find函数
---@param1 array 数组
---@param2 elem 目标元素
--------------------------------------------------------

table.find = function(array,elem) 
	for idx,e in ipairs(array) do 
		if e == elem then 
			return idx
		end
	end
end

--------------------------------------------------------
--- 新增table.shuffle函数
--- @param1 array 数组
--------------------------------------------------------

table.shuffle = function(array)
	for i = 1,#array do 
		local r = math.random(1,#array)
		array[i],array[r] = array[r],array[i]
	end
	return array
end

--------------------------------------------------------
--- 新增table.copy函数
--- @param1 array 数组
--------------------------------------------------------

function table.copy(object)
    -- 已经复制过的table，key为复制源table，value为复制后的table
    -- 为了防止table中的某个属性为自身时出现死循环
    -- 避免本该是同一个table的属性，在复制时变成2个不同的table(内容同，但是地址关系和原来的不一样了)
    local lookup_table = {}
    local function _copy(object)
        if type(object) ~= 'table' then -- 非table类型都直接返回
            return object
        elseif lookup_table[object] then
            return lookup_table[object]
        end 
        local new_table = {}
        lookup_table[object] = new_table
        for k,v in pairs(object) do
            new_table[_copy(k)] = _copy(v) 
        end 
        -- 这里直接拿mt来用是因为一般对table操作不会很粗暴的修改mt的相关内容
        return setmetatable(new_table, getmetatable(object))
    end 
    return _copy(object)                    
end

--------------------------------------------------------
--- 新增table.print_lua_table函数
--- @param1 array 数组
--------------------------------------------------------

local function print_lua_table(lua_table, indent)
	indent = indent or 0
	for k, v in pairs(lua_table) do
		if type(k) == "string" then
			k = string.format("%q", k)
		end
		local szSuffix = ""
		if type(v) == "table" then
			szSuffix = "{"
		end
		local szPrefix = string.rep("    ", indent)
		formatting = szPrefix.."["..k.."]".." = "..szSuffix
		if type(v) == "table" then
			print(formatting)
			print_lua_table(v, indent + 1)
			print(szPrefix.."},")
		else
			local szValue = ""
			if type(v) == "string" then
				szValue = string.format("%q", v)
			else
				szValue = tostring(v)
			end
			print(formatting..szValue..",")
		end
	end
end

table.print_lua_table = print_lua_table

-- 打印当前堆栈
function PrintNowStackTrace()
	local stackTrace = debug.traceback("", 2)
	print("[PrintNowStackTrace] 堆栈信息 "..stackTrace)
end