----------------------------------------------------------------
-- 文件名: LibTimer.lua
-- 日期: 2013-12-03
-- 描述: 计时器类库
-- 作者: 盒子哥
-- 特别感谢ace
----------------------------------------------------------------

local MAJOR, MINOR = "LibTimer", 1
local LibTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)

LibTimer.hash = {}
LibTimer.selfs = {}
LibTimer.cache = {}
LibTimer.frame = CEGUI.System:getSingleton():getGUISheet()

local table = table
local type = type
local tostring = tostring
--[[
	 xpcall safecall implementation
]]
local xpcall = xpcall

local function errorhandler(err)
	return geterrorhandler()(err)
end

local function CreateDispatcher(argCount)
	local code = [[
		local xpcall, eh = ...	-- our arguments are received as unnamed values in "..." since we don't have a proper function declaration
		local method, ARGS
		local function call() return method(ARGS) end
	
		local function dispatch(func, ...)
			 method = func
			 if not method then return end
			 ARGS = ...
			 return xpcall(call, eh)
		end
	
		return dispatch
	]]
	
	local ARGS = {}
	for i = 1, argCount do ARGS[i] = "arg"..i end
	code = code:gsub("ARGS", table.concat(ARGS, ", "))
	return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler)
end

local Dispatchers = setmetatable({}, {
	__index=function(self, argCount)
		local dispatcher = CreateDispatcher(argCount)
		rawset(self, argCount, dispatcher)
		return dispatcher
	end
})
Dispatchers[0] = function(func)
	return xpcall(func, errorhandler)
end
 
local function safecall(func, ...)
	return Dispatchers[select('#', ...)](func, ...)
end

-- --------------------------------------------------------------------
-- OnUpdate handler
--
local function _OnUpdate(args)
	local update_args = CEGUI.toUpdateEventArgs(args);
	local elapse = update_args.d_timeSinceLastFrame * 1000;

	for index, timer in ipairs(LibTimer.hash) do
		if (timer and timer.callback) then
			timer.duration = timer.duration + elapse
			if (timer.duration > timer.when) then
				timer.duration = 0
				local callback = timer.callback
				if type(callback) == "string" then
					safecall(timer.object[callback], timer.object, timer.arg, elapse)
				else
					safecall(callback, timer.arg, elapse)
				end

				-- 只执行一次的计时器
				if (timer.onetime) then
					local hande = tostring(timer)
					LibTimer.CancelTimer(timer.object, hande, true)
					LibTimer.selfs[timer.object][tostring(timer)] = nil
				end
			end
		end
	end	
end

local function getTimer()
	if (#LibTimer.cache > 0) then
		return table.remove(LibTimer.cache, 1)
	else
		return {}
	end
end
-- ---------------------------------------------------------------------
-- Reg( callback, delay, arg, repeating )
--
-- callback( function or string ) - 回调函数或者方法名
-- delay(int) -延迟时间, 单位: 毫秒
-- arg(variant) - 传递给回调函数的参数
-- repeating(boolean) - 是否重复执行, 还是只执行一次
--
-- 返回这个timer的句柄, 用于取消该计时
local function Reg(self, callback, delay, arg, repeating)
	if type(callback) ~= "string" and type(callback) ~= "function" then 
		local error_origin = repeating and "ScheduleRepeatingTimer" or "ScheduleTimer"
		error(MAJOR..": " .. error_origin .. "(callback, delay, arg): 'callback' - function or method name expected.", 3)
	end
	if type(callback) == "string" then
		if type(self)~="table" then
			local error_origin = repeating and "ScheduleRepeatingTimer" or "ScheduleTimer"
			error(MAJOR..": " .. error_origin .. "(\"methodName\", delay, arg): 'self' - must be a table.", 3)
		end
		if type(self[callback]) ~= "function" then 
			local error_origin = repeating and "ScheduleRepeatingTimer" or "ScheduleTimer"
			error(MAJOR..": " .. error_origin .. "(\"methodName\", delay, arg): 'methodName' - method not found on target object.", 3)
		end
	end	

	local timer = getTimer()	
	
	timer.object = self
	timer.callback = callback
	timer.onetime = ((not repeating) and delay)
	timer.arg = arg
	timer.duration = 0
	timer.when = delay
	if (not timer.index) then
		timer.index = #LibTimer.hash + 1
		table.insert(LibTimer.hash, timer)
	end
	
	-- Insert timer in our self->handle->timer registry
	local handle = tostring(timer)
	
	local selftimers = LibTimer.selfs[self]
	if not selftimers then
		selftimers = {}
		LibTimer.selfs[self] = selftimers
	end
	selftimers[handle] = timer
	
	return handle
end

function LibTimer:ScheduleTimer(callback, delay, arg)
	return Reg(self, callback, delay, arg)
end

function LibTimer:ScheduleRepeatingTimer(callback, delay, arg)	
	return Reg(self, callback, delay, arg, true)
end

function LibTimer:CancelTimer(handle, silent)
	if not handle then return end -- nil handle -> bail out without erroring
	if type(handle) ~= "string" then
		error(MAJOR..": CancelTimer(handle): 'handle' - expected a string", 2)	-- for now, anyway
	end
	local selftimers = LibTimer.selfs[self]
	local timer = selftimers and selftimers[handle]
	if silent then
		if timer then
			timer.callback = nil
			table.insert(LibTimer.cache, timer)
		end
		return not not timer	-- might return "true" even if we double-cancel. we'll live.
	else
		if not timer then
			geterrorhandler()(MAJOR..": CancelTimer(handle[, silent]): '"..tostring(handle).."' - no such timer registered")
			return false
		end
		if not timer.callback then 
			geterrorhandler()(MAJOR..": CancelTimer(handle[, silent]): '"..tostring(handle).."' - timer already cancelled or expired")
			return false
		end
		timer.callback = nil	-- don't run it again
		table.insert(LibTimer.cache, timer)
		return true
	end
end

function LibTimer:CancelAllTimers()
	if not(type(self) == "string" or type(self) == "table") then
		error(MAJOR..": CancelAllTimers(): 'self' - must be a string or a table",2)
	end
	if self == LibTimer then
		error(MAJOR..": CancelAllTimers(): supply a meaningful 'self'", 2)
	end
	
	local selftimers = LibTimer.selfs[self]
	if selftimers then
		for handle,v in pairs(selftimers) do
			if type(v) == "table" then
				LibTimer.CancelTimer(self, handle, true)
			end
		end
	end
end

-- ---------------------------------------------------------------------
-- Embed handling

LibTimer.embeds = LibTimer.embeds or {}

local mixins = {
	"ScheduleTimer", "ScheduleRepeatingTimer", 
	"CancelTimer", "CancelAllTimers",
}

function LibTimer:Embed(target)
	LibTimer.embeds[target] = true
	for _,v in pairs(mixins) do
		target[v] = LibTimer[v]
	end
	return target
end

function LibTimer:OnEmbedDisable( target )
	target:CancelAllTimers()
end

LibTimer.frame:subscribeEvent("WindowUpdate", _OnUpdate);