TIMERS_VERSION = "1.05"

--[[

  -- A timer running every second that starts immediately on the next frame, respects pauses
  Timers:CreateTimer(function()
      print ("Hello. I'm running immediately and then every second thereafter.")
      return 1.0
    end
  )

  -- The same timer as above with a shorthand call 
  Timers(function()
    print ("Hello. I'm running immediately and then every second thereafter.")
    return 1.0
  end)
  

  -- A timer which calls a function with a table context
  Timers:CreateTimer(GameMode.someFunction, GameMode)

  -- A timer running every second that starts 5 seconds in the future, respects pauses
  Timers:CreateTimer(5, function()
      print ("Hello. I'm running 5 seconds after you called me and then every second thereafter.")
      return 1.0
    end
  )

  -- 10 second delayed, run once using gametime (respect pauses)
  Timers:CreateTimer({
    endTime = 10, -- when this timer should first execute, you can omit this if you want it to run first on the next frame
    callback = function()
      print ("Hello. I'm running 10 seconds after when I was started.")
    end
  })

  -- 10 second delayed, run once regardless of pauses
  Timers:CreateTimer({
    useGameTime = false,
    endTime = 10, -- when this timer should first execute, you can omit this if you want it to run first on the next frame
    callback = function()
      print ("Hello. I'm running 10 seconds after I was started even if someone paused the game.")
    end
  })


  -- A timer running every second that starts after 2 minutes regardless of pauses
  Timers:CreateTimer("uniqueTimerString3", {
    useGameTime = false,
    endTime = 120,
    callback = function()
      print ("Hello. I'm running after 2 minutes and then every second thereafter.")
      return 1
    end
  })


  -- A timer using the old style to repeat every second starting 5 seconds ahead
  Timers:CreateTimer("uniqueTimerString3", {
    useOldStyle = true,
    endTime = GameRules:GetGameTime() + 5,
    callback = function()
      print ("Hello. I'm running after 5 seconds and then every second thereafter.")
      return GameRules:GetGameTime() + 1
    end
  })

]]



TIMERS_THINK = 0.01

if Timers == nil then
	print('[Timers] creating Timers')
	Timers = {}
	setmetatable(Timers, {
		__call = function(t, ...)
			return t:CreateTimer(...)
		end
	})
	--Timers.__index = Timers
end

function Timers:start()
	Timers = self
	self.timers = {}

	--local ent = Entities:CreateByClassname("info_target") -- Entities:FindByClassname(nil, 'CWorld')
	local ent = SpawnEntityFromTableSynchronous("info_target", { targetname = "timers_lua_thinker" })
	ent:SetThink("Think", self, "timers", TIMERS_THINK)
end

function Timers:Think()
	--if GameRules:State_Get() >= DOTA_GAMERULES_STATE_POST_GAME then
	--return
	--end

	-- Track game time, since the dt passed in to think is actually wall-clock time not simulation time.
	local now = GameRules:GetGameTime()

	-- Process timers
	for k, v in pairs(Timers.timers) do
		local bUseGameTime = true
		if v.useGameTime ~= nil and v.useGameTime == false then
			bUseGameTime = false
		end
		local bOldStyle = false
		if v.useOldStyle ~= nil and v.useOldStyle == true then
			bOldStyle = true
		end

		local now = GameRules:GetGameTime()
		if not bUseGameTime then
			now = Time()
		end

		if v.endTime == nil then
			v.endTime = now
		end
		-- Check if the timer has finished
		if now >= v.endTime then
			-- Remove from timers list
			Timers.timers[k] = nil

			Timers.runningTimer = k
			Timers.removeSelf = false

			-- Run the callback
			local status, nextCall
			if v.context then
				status, nextCall = xpcall(function()
					return v.callback(v.context, v)
				end, function(msg)
					return msg .. '\n' .. debug.traceback() .. '\n'
				end)
			else
				status, nextCall = xpcall(function()
					return v.callback(v)
				end, function(msg)
					return msg .. '\n' .. debug.traceback() .. '\n'
				end)
			end

			Timers.runningTimer = nil

			-- Make sure it worked
			if status then
				-- Check if it needs to loop
				if nextCall and not Timers.removeSelf then
					-- Change its end time

					if bOldStyle then
						v.endTime = v.endTime + nextCall - now
					else
						v.endTime = v.endTime + nextCall
					end

					Timers.timers[k] = v
				end

				-- Update timer data
				--self:UpdateTimerData()
			else
				-- Nope, handle the error
				Timers:HandleEventError('Timer', k, nextCall)
			end
		end
	end

	return TIMERS_THINK
end

function Timers:HandleEventError(name, event, err)
	print(err)

	-- Ensure we have data
	name = tostring(name or 'unknown')
	event = tostring(event or 'unknown')
	err = tostring(err or 'unknown')

	-- Tell everyone there was an error
	--Say(nil, name .. ' threw an error on event '..event, false)
	--Say(nil, err, false)

--[[
	api:Message({
		name = name,
		event = event,
		err = err
	}, 3);
--]]

	-- Prevent loop arounds
	if not self.errorHandled then
		-- Store that we handled an error
		self.errorHandled = true
	end
end

function Timers:CreateTimer(name, args, context)
	if type(name) == "function" then
		if args ~= nil then
			context = args
		end
		args = { callback = name }
		name = DoUniqueString("timer")
	elseif type(name) == "table" then
		args = name
		name = DoUniqueString("timer")
	elseif type(name) == "number" then
		args = { endTime = name, callback = args }
		name = DoUniqueString("timer")
	end
	if not args.callback then
		print("Invalid timer created: " .. name)
		return
	end

	local now = GameRules:GetGameTime()
	if args.useGameTime ~= nil and args.useGameTime == false then
		now = Time()
	end

	if args.endTime == nil then
		args.endTime = now
	elseif args.useOldStyle == nil or args.useOldStyle == false then
		args.endTime = now + args.endTime
	end

	args.context = context

	Timers.timers[name] = args

	return name
end

function Timers:RemoveTimer(name)
	Timers.timers[name] = nil
	if Timers.runningTimer == name then
		Timers.removeSelf = true
	end
end

function Timers:RemoveTimers(killAll)
	local timers = {}
	Timers.removeSelf = true

	if not killAll then
		for k, v in pairs(Timers.timers) do
			if v.persist then
				timers[k] = v
			end
		end
	end

	Timers.timers = timers
end

if not Timers.timers then
	Timers:start()
end

GameRules.Timers = Timers