--[[
####################################################
Eva Framework beta3
simonw@cwowaddon.com last modify on 2008.5.18
Copyright GNU GPL

Doc: http://wiki.cwowaddon.com/EVA
Svn: http://woweva.googlecode.com
Site: http://sites.google.com/a/cwowaddon.com/eva
Publish: http://bbs.cwowaddon.com/thread-585-1-1.html
####################################################

blockN = {
	Name = "block name",
	Description = "Description of cmd",
	Enabled = true,
	Type = "Public"|"Private"|"System",
	RequestTypes = {"Event", "SlashCommand", "FrameEvent", "KeyBinding", "UserEvent", ...},
	Events = {"eventNameN", ...},
	FrameEvents = {"frameNameN:eventNameN", ...},
	UserEvents = {"userEventNameN", ...},
	SlashCommands = {"slashCommandN", ...},
	KeyBindings = {"keyN", ...},
	CustomKeyBinding = customFunction,
	Commands = {
		GTimer = {Interval = N, Times =N},
		Timer = {Interval = N, Times =N},
		ThreadMode = "Single"|"Multi",
		{Description = "cmd notes", Function = customFunctionN, Timer = {Interval = N, Times =N}},
		...
	}
}
addon = Eva.Addon:New()
addon:add(EVA.Block:New(block1))
addon:add(EVA.Block:New(block2))
...

Eva.AddonRegistry:Add(addon)
]]

local Version = Eva.Version
local pairs = pairs
local ipairs = ipairs
local EmptyFunction = function() end

-------------------------------------------------------------------------------
--Registry
--self = {key = true, key = {value = true}, key = {value = obj}, key = Registry{}, ...}
-------------------------------------------------------------------------------
local Registry = {__Name = "Registry"}

function Registry:_New(obj)
	local o = obj or {}
	setmetatable(o, self)
	self.__index = self
	self.__mode = "\'k\'"
	return o
end

function Registry:_Count()
	local i = 0
	for _, _ in pairs(self) do
		i = i + 1
	end
	return i
end

function Registry:New(obj)
	local o = self:_New(obj)
	o._items = {}
	return o
end

function Registry:Add(key, value, obj)
	if (not key) then return end

	local v = self._items[key]

	if (value == nil and v == nil) then
		self._items[key] = true
	elseif (value ~= nil and type(v) == "table") then
		v[value] = obj or true
	elseif (value ~= nil and type(v) ~= "table") then
		local t = Registry:_New()
		t[value] = obj or true
		self._items[key] = t
	end
end

function Registry:Remove(key, value)
	if (not key) then return end

	if (key and value) then
		local v = self._items[key]
		if (type(v) == "table") then
			v[value] = nil
			if (v:_Count() == 0) then
				self._items[key] = nil
			end
		end
	else
		self._items[key] = nil
	end
end

function Registry:GetValue(key)
	if (not key) then return end
	return self._items[key]
end

function Registry:GetByIndex(index)
	local i = 1
	for k, v in pairs(self._items) do
		if (i == index) then
			return k, v
		end
		i = i + 1
	end
end

function Registry:Iterator()
	return pairs(self._items)
end

function Registry:Reset()
	--for k, v in pairs(self._items) do
	--	self._items[k] = nil
	--end
	self._items = {}
end

function Registry:Count()
	return Registry._Count(self._items)
end

--[[
function Registry:Dump()
	for k, v in pairs(self._items) do
		print(tostring(k).."|"..tostring(v))

		if (type(v) == "table") then
			for kk, vv in pairs(v) do
				print("    "..tostring(kk).."|---|"..tostring(vv))
			end
		end
		print("----")
	end
end]]

-------------------------------------------------------------------------------
--Event
-------------------------------------------------------------------------------
local Event = Registry:New(obj)

Event.__Name = "Event"

function Event:New(obj)
	local o = Registry.New(self, obj)
	self.__call = self._Call
	return o
end

function Event._Call(t, ...)
	for source, funcs in t:Iterator() do
		for func, _ in pairs(funcs) do
			func(source, ...)
		end
	end
end

function Event:Reset()
	for source, funcs in self:Iterator() do
		for func, _ in pairs(funcs) do
			funcs[func] = nil
		end
	end
end

-------------------------------------------------------------------------------
--Light Event
-------------------------------------------------------------------------------
local lEvent = {__Name = "lEvent"}

function lEvent:New(obj)
	local o = obj or {}
	setmetatable(o, self)
	self.__index = self
	self.__call = self._Call
	return o
end

function lEvent:Add(source, func)
	self._source = source
	self._func = func
end

function lEvent._Call(t, ...)
	if (t._func) then
		t._func(t._source, ...)
	end
end

function lEvent:Reset()
	self._source = nil
	self._func = nil
end

-------------------------------------------------------------------------------
--RequestHandler
-------------------------------------------------------------------------------
local RequestHandler = {__Name = "RequestHandler"}

function RequestHandler:New(obj)
	local o = obj or {}
	setmetatable(o, self)
	self.__index = self
	return o
end

function RequestHandler:Process(context)
	error(self.__Name..":Process not to be implemented.")
end

-------------------------------------------------------------------------------
--VAR
-------------------------------------------------------------------------------
local Timer = {
	__Name = "Timer",
	Interval = 0,
	Times = 1,
	_timesCounter = 0,
	_timeElapsed = 0,
}

local Block = {
	__Name = "Block",
	Name = "unnamed block",
	Description = "",
	Type = "Public",
	--Events = {},
	--FrameEvents = {},
	--SlashCommands = {},
	Enabled = true,
	Commands = {},
}

local Command = {
	__Name = "Command",
	ThreadMode = "Single",
	_HandoffCommand = {Function = EmptyFunction},
}

local Addon = Registry:New{
	__Name = "Addon",
	Name = "",
	Version = "",
	EvaVersion = nil,
	CanEnabled = true,
	CanDisabled = true,
	_Enabled = true,
	OnRegister = EmptyFunction,
	OnUnregister = EmptyFunction,
}

--readonly table for store native request type
local RequestType = {
	__Name = "RequestType",
	Event = "Event",
	FrameEvent = "FrameEvent",
	KeyBinding = "KeyBinding",
	SlashCommand = "SlashCommand"
}

local UserEvent = {__Name = "UserEvent"}

local EvaFrame = {__Name = "EvaFrame"}

local ContextManager = {__Name = "ContextManager"}

local Context = {__Name = "Context", Event_Args = {IsClonable = true}}

local EventHandler = RequestHandler:New{__Name = "EventHandler"}

local KeyBindingHandler = RequestHandler:New{__Name = "KeyBindingHandler"}

local SlashCommandHandler = RequestHandler:New{__Name = "SlashCommandHandler"}

local AddonRegistry = Registry:New{__Name = "AddonRegistry"}

local TimerRegistry = Registry:New{__Name = "TimerRegistry"}

local RequestRegistry = Registry:New{__Name = "RequestRegistry"}
RequestRegistry:Add(RequestType.Event, RequestType.Event, EventHandler)
RequestRegistry:Add(RequestType.FrameEvent, RequestType.FrameEvent, EventHandler)
RequestRegistry:Add(RequestType.KeyBinding, RequestType.KeyBinding, EventHandler)
RequestRegistry:Add(RequestType.SlashCommand, RequestType.SlashCommand, SlashCommandHandler)
RequestRegistry:Add(RequestType.Event, "UserEvent", EventHandler)

local EventRegistry = Registry:New{__Name = "EventRegistry"}

local FrameEventRegistry = Registry:New{__Name = "FrameEventRegistry"}

local KeyBindingRegistry = Registry:New{__Name = "KeyBindingRegistry"}

local SlashCommandRegistry = Registry:New{__Name = "SlashCommandRegistry"}

local UserEventRegistry = Registry:New{__Name = "UserEventRegistry"}

local UserEventSystemRegistry = Registry:New{
	__Name = "UserEventSystemRegistry",
	_map = Registry:New{__Name = "UserEventSystemRegistry.Map"}
}


-------------------------------------------------------------------------------
--Timer
-------------------------------------------------------------------------------
function Timer:New(obj)
	local timer = obj or {}
	setmetatable(timer, self)
	self.__index = self

	if (not timer._StartEvent) then
		timer._StartEvent = lEvent:New()
		timer._EndEvent = lEvent:New()

		timer._StartEvent:Add(TimerRegistry, TimerRegistry.Timer_Start)
		timer._EndEvent:Add(TimerRegistry, TimerRegistry.Timer_End)

		timer.ElapsedEvent = lEvent:New()
		timer.AfterEndEvent = lEvent:New()
	end

	return timer
end

function Timer:Start()
	if (self.Interval <= 0) then
		for i = 1, self.Times, 1 do
			self.ElapsedEvent(self)
		end
		self.AfterEndEvent(self)
	else
		self._StartEvent(self)
	end
end

function Timer:ActiveNow()
	self._timeElapsed = self.Interval
end

function Timer:Resume()
	self._StartEvent(self)
end

function Timer:Pause()
	self._EndEvent(self)
end

function Timer:End()
	self._EndEvent(self)
	self:Reset()
	self.AfterEndEvent(self)
end

function Timer:Terminate()
	self._EndEvent(self)
	self:Reset()
	--self.ElapsedEvent:Reset()
	self.AfterEndEvent:Reset()
end

function Timer:SetValue(interval, times)
	self.Interval = interval
	self.Times = times
end

function Timer:Reset()
	self._timesCounter = 0
	self._timeElapsed = 0
end

function Timer:Elapsed(elapsed)
	self._timeElapsed = self._timeElapsed + elapsed

	if (self:_IsExit()) then
		return
	end

	if (self._timeElapsed > self.Interval) then
		self._timesCounter = self._timesCounter + 1
		self._timeElapsed = 0
		self.ElapsedEvent(self)

		if (self:_IsExit()) then
			return
		end
	end
end

function Timer:_IsExit()
	if (self.Times >= 0 and self._timesCounter >= self.Times) then
		self:End()
		return true
	end
end

-------------------------------------------------------------------------------
--TimerRegistry
-------------------------------------------------------------------------------
function TimerRegistry:Listen(elapsed)
	for timer in self:Iterator() do
		timer:Elapsed(elapsed)
	end
end

function TimerRegistry:Timer_Start(sender)
	self:Add(sender)
end

function TimerRegistry:Timer_End(sender)
	self:Remove(sender)
end

-------------------------------------------------------------------------------
--Context
-------------------------------------------------------------------------------
function ContextManager:Reset(context)
	for key, value in pairs(context) do
		if (type(value) == "table" and value.IsClonable) then
			for k, v in pairs(value) do
				value[k] = nil
			end
			value.IsClonable = true
		else
			context[key] = nil
		end
	end
end

function ContextManager:SetClonable(obj)
	if (obj) then
		obj.IsClonable = true
		return obj
	end
end

function ContextManager:Transfer(context, obj)
	if (obj.Context) then
		self:Reset(obj.Context)
	else
		obj.Context = {}
	end

	self:Clone(context, obj.Context)
end

function ContextManager:Clone(context1, context2)
	for key, value in pairs(context1) do
		if (type(value) == "table" and value.IsClonable) then
			local context2value = context2[key] or {}
			for k, v in pairs(value) do
				context2value[k] = v
			end
			context2[key] = context2value
		else
			context2[key] = value
		end
	end
end

function ContextManager:SetEvent_Args(context, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
	context.Event_Args[1] = arg1
	context.Event_Args[2] = arg2
	context.Event_Args[3] = arg3
	context.Event_Args[4] = arg4
	context.Event_Args[5] = arg5
	context.Event_Args[6] = arg6
	context.Event_Args[7] = arg7
	context.Event_Args[8] = arg8
	context.Event_Args[9] = arg9
end

-------------------------------------------------------------------------------
--Frame
-------------------------------------------------------------------------------
function EvaFrame:Init(name)
	self.Instance = CreateFrame("Frame", name)
	self.Instance:SetScript("OnEvent", self.OnEvent)
	self.Instance:SetScript("OnUpdate", self.OnUpdate)
	self.Instance:RegisterEvent("VARIABLES_LOADED")
	self.Instance:RegisterEvent("PLAYER_LOGIN")
end

function EvaFrame:OnEvent()
	if (event == "VARIABLES_LOADED") then
		AddonRegistry:Init(EventRegistry)
		AddonRegistry:Init(FrameEventRegistry)
		AddonRegistry:Init(SlashCommandRegistry)
		AddonRegistry:Init(UserEventRegistry)
	elseif (event == "PLAYER_LOGIN") then
		AddonRegistry:Init(KeyBindingRegistry)
	end

	EventRegistry:Listen(event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
end

function EvaFrame:OnUpdate()
	TimerRegistry:Listen(arg1)
end

-------------------------------------------------------------------------------
--Addon
-------------------------------------------------------------------------------
--1:= 2:> 3:< 4:Invalid
function Addon:CheckEvaVersion()
	if (not self.EvaVersion) then
		return 4
	end

	if (string.low(Version) == string.low(self.EvaVersion)) then
		return 1
	end

	local eva = tonumber(Version)
	local addon = tonumber(self.EvaVersion)

	if (eva and addon) then
		if (addon > eva) then
			return 2
		else
			return 3
		end
	elseif (eva and not addon) then
		return 3
	elseif (not eva and addon) then
		return 2
	end

	if (self.EvaVersion > Version) then
		return 2
	else
		return 3
	end
	return 4
end

function Addon:GetChild(blockname)
	for block in self:Iterator() do
		if (block.Name == blockname) then
			return block
		end
	end
end

-------------------------------------------------------------------------------
--Block
-------------------------------------------------------------------------------
function Block:New(obj)
	local block = obj or {}
	setmetatable(block, self)
	self.__index = self
	return block
end

function Block:Refresh()
	AddonRegistry:_UnregisterBlock(self)
	AddonRegistry:_RegisterBlock(self)
end

function Block:Run()
	local commands = Command:New(self.Commands)
	commands:Schedule()
end

-------------------------------------------------------------------------------
--UserEvent
-------------------------------------------------------------------------------
function UserEvent:New()
	local o = obj or {}
	setmetatable(o, self)
	self.__index = self
	return o
end

function UserEvent:Filter(source, ...)
	error(self.__Name..":Filter not to be implemented.")
end

-------------------------------------------------------------------------------
--Command
-------------------------------------------------------------------------------
function Command:New(obj)
	local cmd = obj or {}
	setmetatable(cmd, self)
	self.__index = self

	Command._SetIsContainer(cmd)

	if (cmd.IsContainer) then
		cmd.GTimer = cmd.GTimer or {Interval = 0, Times = 1}
		cmd.Timer = cmd.Timer or {Interval = 0, Times = 1}
	end
	return cmd
end

function Command:Schedule()
	if (self.IsContainer and self.GTimer.Times == 0) then
		return
	end

	local timer = self:_CreateTimer()

	local context = timer.Context
	context.Timer = timer
	context.Source = self
	context.CommandIndex = 1
	context.GTimesCounter = 0
	context.TimerInterval = timer.Interval
	context.TimerTimes = timer.Times

	local source = self
	if (self.IsContainer) then
		source = self[context.CommandIndex]
	end

	Command._TimerStart(timer, source)
end

--Only command
function Command:Run(timer)
	local context
	if (timer) then
		context = timer.Context
	end

	local func
	if (type(self.Function) == "string") then
		func = getglobal(self.Function)
	else
		func = self.Function
	end

	if (func) then
		--EvaFrame.Instance:Hide()
		func(context)
		--EvaFrame.Instance:Show()
	end
end

function Command:_CreateTimer()
	local timer
	if (self.IsContainer and self.ThreadMode == "Multi") then
		timer = Timer:New({Interval = self.Timer.Interval, Times = self.Timer.Times})
		--timer.Context = Context:Clone()
	else
		timer = Timer:New(self.Timer)
		--timer.Context = Context
	end

	if (timer.Context) then
		timer.Interval = timer.Context.TimerInterval or timer.Interval
		timer.Times = timer.Context.TimerTimes or timer.Times
	end

	ContextManager:Transfer(Context, timer)

	--print(tostring(timer.Context or "nil"))

	return timer
end

function Command._SetIsContainer(command)
	if (table.getn(command) > 0) then
		command.IsContainer = true
	else
		command.IsContainer = false
	end
end

function Command._TimerStart(timer, command)
	local context = timer.Context
	local cmd = Command:New(command)

	timer:Reset()
	timer.ElapsedEvent:Reset()
	timer.AfterEndEvent:Reset()
	timer.ElapsedEvent:Add(cmd, cmd.Run)
	timer.AfterEndEvent:Add(cmd, cmd._OnHandoff)

	if (command.Timer) then
		timer:SetValue(command.Timer.Interval, command.Timer.Times)
	else
		timer:SetValue(context.TimerInterval, context.TimerTimes)
	end

	timer:Start()
end

--Only command
--当nextCommand存在的时候挂接nextCommand, 当不存在的时候检测GTimer开始组的轮回
function Command:_OnHandoff(sender)
	local timer = sender
	local context = timer.Context

	if (not context.Source.IsContainer) then
		return
	end

	local commands = context.Source
	context.CommandIndex = context.CommandIndex + 1
	local nextCommand = commands[context.CommandIndex]

	--DEFAULT_CHAT_FRAME:AddMessage( string.format("CMD:%s %s %s"), context.CommandIndex, nextCommand.Timer.Interval, nextCommand.Timer.Times)

	if (nextCommand) then
		Command._TimerStart(timer, nextCommand)
	else
		if (context.CommandIndex == 0) then
			--组间等待后, 开始从头执行
			context.CommandIndex = 1
			Command._TimerStart(timer, commands[context.CommandIndex])
		else
			--开始执行组间等待
			self:_OnGroupRunAtEnd(timer)
		end
	end
end

--Only command
function Command:_OnGroupRunAtEnd(timer)
	local context = timer.Context
	local commands = context.Source
	if (context.GTimesCounter + 1 < commands.GTimer.Times) then
		context.GTimesCounter = context.GTimesCounter + 1
		context.CommandIndex = -1
		commands._HandoffCommand.Timer = commands._HandoffCommand.Timer or {Interval = commands.GTimer.Interval, Times = 1}
		--commands._HandoffCommand.Timer = {Interval = commands.GTimer.Interval, Times = 1}
		Command._TimerStart(timer, commands._HandoffCommand)
	end
end

-------------------------------------------------------------------------------
--RequestRegistry

--self = {nativeRequestType = {requestType = handler, ...}, ...}
-------------------------------------------------------------------------------
function RequestRegistry:Add(nativeRequestType, requestType, handler)
	if (not RequestType[nativeRequestType]) then
		error(self.__Name..":Add nativeRequestType is not a native RequestType.")
	end

	local requests = self:GetValue(nativeRequestType)

	if (requests and requests[requestType]) then
		error(self.__Name..":Add requestType already exist.")
	end

	Registry.Add(self, nativeRequestType, requestType, handler)
end

function RequestRegistry:Schedule(block, nativeRequestType, context)
	if (not block.Enabled or block.Type == "System") then return end

	context.Block = block
	local requests = self:GetValue(nativeRequestType)

	for _, requestType in ipairs(block.RequestTypes) do
		local handler = requests[requestType]
		context.RequestType = requestType
		if (handler and handler:Process(context)) then
			return
		end
	end
end

-------------------------------------------------------------------------------
--AddonRegistry

--self = {addon1, addon2, ...}
-------------------------------------------------------------------------------
function AddonRegistry:Init(registry)
	for addon in self:Iterator() do
		--if (addon._Enabled) then
			for block in addon:Iterator() do
				registry:Register(block)
			end
		--end
	end
end

function AddonRegistry:Register(addon)
	if (addon.CanEnabled and not addon._Enabled) then
		addon._Enabled = true
		addon:OnRegister()
		for block in addon:Iterator() do
			self:_RegisterBlock(block)
		end
		return true
	end
end

function AddonRegistry:Unregister(addon)
	if (addon.CanDisabled and addon._Enabled) then
		addon._Enabled = false
		for block in addon:Iterator() do
			self:_UnregisterBlock(block)
		end
		addon:OnUnregister()
		return true
	end
end

function AddonRegistry:GetChild(addonname)
	for addon in self:Iterator() do
		if (addon.Name == addonname) then
			return addon
		end
	end
end

function AddonRegistry:_RegisterBlock(block)
	EventRegistry:Register(block)
	FrameEventRegistry:Register(block)
	KeyBindingRegistry:Register(block)
	SlashCommandRegistry:Register(block)
	UserEventRegistry:Register(block)
end

function AddonRegistry:_UnregisterBlock(block)
	EventRegistry:Unregister(block)
	FrameEventRegistry:Unregister(block)
	KeyBindingRegistry:Unregister(block)
	SlashCommandRegistry:Unregister(block)
	UserEventRegistry:Unegister(block)
end

-------------------------------------------------------------------------------
--KeyBinding
-------------------------------------------------------------------------------

-------------------------------------------------------------------------------
--KeyBindingRegistry
-------------------------------------------------------------------------------
function KeyBindingRegistry:Register(block)
	local button
	if (block.CustomKeyBinding) then
		button = block.CustomKeyBinding()
		if (button) then
			button.evafunc = KeyBindingRegistry.Listen(block)
		end
	elseif (block.KeyBindings) then
		local bid = string.sub(tostring(block), 8)
		button = CreateFrame("Button", "EvaButton"..bid, UIParent, "SecureActionButtonTemplate")
		button:SetAttribute("type", "evafunc")
		button.evafunc = KeyBindingRegistry.Listen(block)

		for _, key in ipairs(block.KeyBindings) do
			SetOverrideBindingClick(button, false, key, button:GetName())
		end
	end
	block.Button = button
end

function KeyBindingRegistry:Unregister(block)
	if (block.Button) then
		ClearOverrideBindings(block.Button)
	end
end

function KeyBindingRegistry.Listen(block)
	return function (frame, unit, button)
		--Context.Keys = keys
		Context.Key_Frame = frame
		Context.Key_Unit = unit
		Context.Key_Button = button

		RequestRegistry:Schedule(block, RequestType.KeyBinding, Context)

		--UserEventSystemRegistry:Trigger(RequestType.KeyBinding, button)
	end
end

-------------------------------------------------------------------------------
--FrameEvent
-------------------------------------------------------------------------------

-------------------------------------------------------------------------------
--FrameEventRegistry
-------------------------------------------------------------------------------
function FrameEventRegistry:Register(block)
	if (not block.FrameEvents) then return end

	for _, frameEvent in ipairs(block.FrameEvents) do
		local frameName, event = self:ParseFrameEvent(frameEvent)
		local frame = getglobal(frameName)
		if (frame and event) then
			frame:SetScript(event, self.Listen(frameEvent, frameName, frame, event))
		end
		self:Add(frameEvent, block)
	end
end

function FrameEventRegistry:Unregister(block)
	for frameEvent, blocks in self:Iterator() do
		for key in pairs(blocks) do
			if (block == key) then
				blocks[key] = nil
			end
		end

		if (Registry._Count(blocks) < 1) then
			local frameName, event = self:ParseFrameEvent(frameEvent)
			local frame = getglobal(frameName)
			if (frame and event) then
				frame:SetScript(event, nil)
			end
		end
	end
end

function FrameEventRegistry:ParseFrameEvent(frameEvent)
	local frameName
	local event
	_, _, frameName, event = string.find(frameEvent or "", "(%S*)[:.](%S*)")
	return frameName, event
end

function FrameEventRegistry.Listen(frameEvent, frameName, frame, event)
	--local args = {arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9}
	return function ()
		local blocks = FrameEventRegistry:GetValue(frameEvent)

		if (blocks) then
			ContextManager:Reset(Context)
			Context.Frame = frame
			Context.Frame_Name = frameName
			Context.Event_Name = event
			ContextManager:SetEvent_Args(Context, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)

			for block in pairs(blocks) do
				RequestRegistry:Schedule(block, RequestType.FrameEvent, Context)
			end
		end

		UserEventSystemRegistry:Trigger(RequestType.FrameEvent, frameEvent)
	end
end

-------------------------------------------------------------------------------
--SlashCommand
-------------------------------------------------------------------------------

-------------------------------------------------------------------------------
--SlashCommandRegistry
-------------------------------------------------------------------------------
function SlashCommandRegistry:Register(block)
	if (block.SlashCommands) then
		local i = false
		local bid = string.sub(tostring(block), 8)
		for index, slash in ipairs(block.SlashCommands) do
			loadstring(string.format("SLASH_%s%d='%s'", bid, index, slash))()
			i = true
		end

		if (i) then
			SlashCmdList[bid] = SlashCommandRegistry.Listen(block)
		end
	end
end

function SlashCommandRegistry:Unregister(block)
	if (block.SlashCommands) then
		local bid = string.sub(tostring(block), 8)
		SlashCmdList[bid] = nil
	end
end

function SlashCommandRegistry.Listen(block)
	return function (arg)
		Context.Slash_Arg = arg
		RequestRegistry:Schedule(block, RequestType.SlashCommand, Context)
		UserEventSystemRegistry:Trigger(RequestType.SlashCommand, arg)
	end
end

-------------------------------------------------------------------------------
--SlashCommandHandler
-------------------------------------------------------------------------------
function SlashCommandHandler:_ParseSlashArg(slashArg)
	if (slashArg) then
		local args = {}
		for arg in string.gmatch(slashArg, "(%S+)") do
			table.insert(args, arg)
		end
		return args
	end
end

function SlashCommandHandler:_SetContext()
	local slashArgs = self:_ParseSlashArg(Context.Slash_Arg)
	Context.Slash_Args = ContextManager:SetClonable(slashArgs)
end

function SlashCommandHandler:Process(context)
	local block = context.Block
	self:_SetContext()
	local commands = Command:New(block.Commands)
	commands:Schedule()
	return true
end

-------------------------------------------------------------------------------
--Event
-------------------------------------------------------------------------------

-------------------------------------------------------------------------------
--EventRegistry

--self = {event = {block1 = true, block2 = true, ...}, ...}
-------------------------------------------------------------------------------
function EventRegistry:Register(block)
	if (not block.Events) then return end

	for _, event in ipairs(block.Events) do
		EvaFrame.Instance:RegisterEvent(event)
		self:Add(event, block)
	end
end

function EventRegistry:Unregister(block)
	for event, blocks in self:Iterator() do
		for key in pairs(blocks) do
			if (block == key) then
				blocks[key] = nil
			end
		end

		if (Registry._Count(blocks) < 1) then
			EvaFrame.Instance:UnregisterEvent(event)
		end
	end
end

function EventRegistry:Listen(event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
	local blocks = self:GetValue(event)

	if (blocks) then
		ContextManager:Reset(Context)
		Context.Event_Name = event
		ContextManager:SetEvent_Args(Context, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)

		for block in pairs(blocks) do
			RequestRegistry:Schedule(block, RequestType.Event, Context)
		end
	end

	UserEventSystemRegistry:Trigger(RequestType.Event, event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
end

-------------------------------------------------------------------------------
--EventHandler
-------------------------------------------------------------------------------
function EventHandler:Process(context)
	local block = context.Block
	local commands = Command:New(block.Commands)
	commands:Schedule()
	return true
end

-------------------------------------------------------------------------------
--UserEventSystemRegistry

--a dynamic registry to hold the available UserEvent name in current program
--self = {source = {userEventName1 = true, userEventName2 = true, ...}, ...}

--a static database to store the UserEvent Object
--self._map = {nativeRequestType = {userEventName = userEventObj}, ...}
-------------------------------------------------------------------------------

--UserEventSystemRegistry:Register(event)
--register UserEvent in UserEventSystemRegistry when it used in user's addon
--Parameters
--event (string) - UserEvent name
function UserEventSystemRegistry:Register(event)
	for nativeRequestType, userEvents in self._map:Iterator() do
		local userEvent = userEvents[event]

		if (userEvent and RequestType[userEvent.RequestType]) then
			for _, source in ipairs(userEvent.Sources) do
				local events = self:GetValue(source)
				if (events and events[userEvent.Name]) then return end
				Registry.Add(UserEventSystemRegistry, source, userEvent.Name)
			end

			local block = Block:New{RequestTypes = {userEvent.RequestType}}

			if (userEvent.RequestType == RequestType.Event) then
				block.Events = userEvent.Sources
				EventRegistry:Register(block)
			elseif (userEvent.RequestType == RequestType.FrameEvents) then
				block.FrameEvents = userEvent.Sources
				FrameEventRegistry:Register(block)
			elseif (userEvent.RequestType == RequestType.KeyBinding) then
				block.KeyBindings = userEvent.Sources
				KeyBindingRegistry:Register(block)
			elseif (userEvent.RequestType == RequestType.SlashCommand) then
				block.SlashCommands = userEvent.Sources
				SlashCommandRegistry:Register(block)
			end
		end
	end
end

function UserEventSystemRegistry:Unregister(event)
	for nativeRequestType, events in self:Iterator() do
		events[event] = nil
	end
end

--UserEventSystemRegistry:Add(userEvent)
--add an user defined UserEvent in UserEventSystemRegistry._map
--Parameters
--userEvent (UserEvent) - UserEvent object by user defined
function UserEventSystemRegistry:Add(userEvent)
	if (not RequestType[userEvent.RequestType]) then
		error(self.__Name..":Add userEvent.RequestType is not a native RequestType.")
	end

	local userEvents = self._map:GetValue(userEvent.RequestType)

	if (userEvents and userEvents[userEvent.Name]) then
		error(self.__Name..":Add userEvent already exist.")
	end
	self._map:Add(userEvent.RequestType, userEvent.Name, userEvent)
end

--UserEventSystemRegistry:Trigger(nativeRequestType, source, [arg1, arg2, ...])
--trigger a user event which register in UserEventSystemRegistry for specified source request type and source event
--Parameters
--nativeRequestType (enum string) - native request type defined in RequestType
--source (string) - a key of native request created
--argN (variable) - other args of native request created
function UserEventSystemRegistry:Trigger(nativeRequestType, source, ...)
	local events = self:GetValue(source)
	if (not events) then return end

	local userEvents = self._map:GetValue(nativeRequestType)

	for event in pairs(events) do
		UserEventRegistry:Listen(userEvents[event]:Filter(source, ...))
	end
end

-------------------------------------------------------------------------------
--UserEventRegistry

--self = {event = {block1 = true, block2 = true, ...}, ...}
-------------------------------------------------------------------------------

function UserEventRegistry:Register(block)
	if (not block.UserEvents) then return end

	for _, event in ipairs(block.UserEvents) do
		UserEventSystemRegistry:Register(event)
		self:Add(event, block)
	end
end

function UserEventRegistry:Unregister(block)
	for event, blocks in self:Iterator() do
		for key in pairs(blocks) do
			if (block == key) then
				blocks[key] = nil
			end
		end

		if (Registry._Count(blocks) < 1) then
			UserEventSystemRegistry:Unregister(event)
		end
	end
end

function UserEventRegistry:Listen(event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
	if (not event) then return end

	local blocks = self:GetValue(event)

	if (not blocks) then return end

	ContextManager:Reset(Context)
	Context.Event_Name = event
	ContextManager:SetEvent_Args(Context, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)

	for block in pairs(blocks) do
		RequestRegistry:Schedule(block, RequestType.Event, Context)
	end
end

-------------------------------------------------------------------------------
--Main
-------------------------------------------------------------------------------
if (not Eva.Addon) then
	--Common Interface
	Eva.Block = Block
	Eva.Addon = Addon
	Eva.AddonRegistry = AddonRegistry
	--Extend Dev Interface
	Eva.Context = Context
	Eva.ContextManager = ContextManager
	Eva.Command = Command
	Eva.RequestType = RequestType
	Eva.RequestHandler = RequestHandler
	Eva.RequestRegistry = RequestRegistry
	Eva.UserEvent = UserEvent
	Eva.UserEventSystemRegistry = UserEventSystemRegistry

	EvaFrame:Init("EvaFrame")
end