--[[---------------------------------------------------------------------------------
  Clique by Cladhaire <cladhaire@gmail.com>
  GUI concept/code by Gello 
  
  TODO:
  
----------------------------------------------------------------------------------]]

--[[---------------------------------------------------------------------------------
  Create the AddOn object and create a local binding for AceLocale
----------------------------------------------------------------------------------]]

Clique = AceLibrary("AceAddon-2.0"):new(
    "AceHook-2.0", 
    "AceConsole-2.0", 
    "AceDB-2.0", 
    "AceEvent-2.0",
    "AceModuleCore-2.0",
    "AceDebug-2.0"
)

Clique:RegisterDB("CliqueDB")
local L = AceLibrary:GetInstance("AceLocale-2.0"):new("Clique")

-- Expoxe AceHook and AceEvent to our modules
Clique:SetModuleMixins("AceHook-2.0", "AceEvent-2.0", "AceDebug-2.0")

--[[---------------------------------------------------------------------------------
  This is the actual addon object
----------------------------------------------------------------------------------]]

function Clique:OnInitialize()
    self:LevelDebug(2, "Clique:OnInitialize()")
    self:CheckProfile()
    
    self:LevelDebug(3, "Setting all modules to inactive.")
    for name,module in self:IterateModules() do
        self:ToggleModuleActive(name, false)
    end
end

function Clique:OnEnable()
    self:LevelDebug(2, "Clique:OnEnable()")
    IndentationLib.addSmartCode(CliqueEditBox)

    if GetCVar("AutoSelfCast") == "1" then
        StaticPopup_Show("CLIQUE_AUTO_SELF_CAST")
        return
    end
    
    -- Register for ADDON_LOADED so we can load plugins for LOD addons
    self:RegisterEvent("ADDON_LOADED", "LoadModules")
    
    -- Build the action table, so we have precompiled functions
	self:ScanSpellbook()
    self:BuildActionTable()

	-- Enable tooltips in the GUI
	self:EnableTooltips()
    self:RegUtilFuncs()
    
    -- Create the hook tables
    self._OnClick = {}
    
    -- Load any valid modules
    self:LoadModules()

    -- Hook the SpellBookFrame so we can hide/show as needed
    self:HookScript(SpellBookFrame, "OnShow", "SpellBookFrame_OnShow")
    for i=1,12 do
        local button = getglobal("SpellButton"..i)
        button:RegisterForClicks("LeftButtonUp","RightButtonUp", "MiddleButtonUp", "Button4Up", "Button5Up");
        self:HookScript(button, "OnClick", "SpellButton_OnClick")
    end
end

function Clique:LoadModules()
    for name,module in self:IterateModules() do
        if not self:IsModuleActive(name) and not module.disabled then
            -- Try to enable the module
            
            local loadModule = nil
                        
            if module.Test and type(module.Test) == "function" then
                if module:Test() then
                    loadModule = true
                end
            else
                loadModule = true
            end
            
            if loadModule and not Clique:IsModuleActive(name) then
                self:LevelDebug(1, "Enabling module \"%s\" for %s.", name, module.fullname)
                Clique:ToggleModuleActive(name,true)
 
                if module._OnClick then
                    self:LevelDebug(2, "Grabbing _OnClick from %s", name)
                    self._OnClick[name] = module
                end
            end
        end
    end
end

function Clique:CheckProfile()
    self:LevelDebug(2, "Clique:CheckProfile()")

    local profile = self.db.char
    profile[L"DEFAULT_FRIENDLY"] = profile[L"DEFAULT_FRIENDLY"] or {}
    profile[L"DEFAULT_HOSTILE"] = profile[L"DEFAULT_HOSTILE"] or {}
end

function Clique:BuildActionTable()
    self:LevelDebug(2, "Clique:BuildActionTable()")
    
    local actions = self:ClearTable(self.Actions)
    self.Actions = actions
    
    for k,v in pairs(self.db.char) do
        actions[k] = {}
        
        for i,entry in ipairs(v) do
            local a = bit.band(entry.modifiers, 1)
            local c = bit.band(entry.modifiers, 2)
            local s = bit.band(entry.modifiers, 4)
            
            -- Skip any non-bound entries
            if entry.button ~= L"BINDING_NOT_DEFINED" then 
                local key = string.format("%s%d", entry.button, entry.modifiers)
                local action = entry.action
                if not action and not entry.custom then
                    local buff = self.spellbook[entry.name]
                    if buff then buff = tonumber(buff) end
                    if self:IsBuff(entry.name) and not entry.rank then
                        action = string.format("Clique:BestRank(\"%s\", Clique.unit)", entry.name)
                    elseif entry.rank then
                        action = string.format("Clique:CastSpell(\""..L["SPELL_FORMAT"].."\")", entry.name, entry.rank)
                    else
                        action = string.format("Clique:CastSpell(\"%s\")", entry.name)
                    end
                end
                
                --self:Print(action)
                
                local func,errString = loadstring(action)
                if func then 
                    actions[k][key] = func
                else
                    DEFAULT_CHAT_FRAME:AddMessage(string.format(L"ERROR_SCRIPT", errString))
                end
            end
        end
    end
end

function Clique:OnClick(button, unit)
    unit = unit or this.unit 
    button = button or arg1
    local a,c,s = IsAltKeyDown() or 0, IsControlKeyDown() or 0, IsShiftKeyDown() or 0 

    local targettarget = nil

    if not unit then
        unit = this:GetParent().unit
        if not unit then
            error(string.format(L"NO_UNIT_FRAME", tostring(this:GetName())))
        end
    end
	
	if not UnitExists(unit) then
		return
	end

    Clique.unit = unit
	-- DEFAULT_CHAT_FRAME:AddMessage("Clique:OnClick("..tostring(button)..", "..tostring(unit)..")")
    if not UnitExists(unit) then return end

    -- If the casting hand is up on the screen, cast the waiting spell on
    -- this unit
    if SpellIsTargeting() then
        if button == "LeftButton" then SpellTargetUnit(unit)
        elseif button == "RightButton" then SpellStopTargeting() end
        return true
    end

    -- If the cursor has an item and we're clicking on another player,
    -- attempt to trade with them (or feed your pet, etc).  If we
    -- LeftButton drop it on ourselves, then equip the item.  If we click
    -- anything else, then put the item back in the backpack
    if CursorHasItem() then
        if button == "LeftButton" then
            if unit == "player" then AutoEquipCursorItem()
            else DropItemOnUnit(unit) end
        else PutItemInBackpack() end
        return
    end

    -- We need to determine which cast set we're coming from
	local default = L"DEFAULT_FRIENDLY"
    local restore = nil

	if UnitCanAttack("player", unit) then
		default = L"DEFAULT_HOSTILE"
	end
    
    Clique.set = default
    
    -- Iterate the hooks here
    for name,module in pairs(Clique._OnClick) do
        if module:_OnClick(button, Clique.unit) then 
            self:LevelDebug(3, "Module %s has changed the clique set.", name)
            break 
        end
    end
	
	if not Clique.set or not Clique.Actions[Clique.set] then
		Clique.set = default
	end

    local modifiers = 0
    modifiers = bit.bor(modifiers, a * 1)
    modifiers = bit.bor(modifiers, c * 2)
    modifiers = bit.bor(modifiers, s * 4)
	
    local key = string.format("%s%d", button, modifiers)
    local func = Clique.Actions[Clique.set][key]
    local entry = Clique.db.char[Clique.set][key]
	
    self:LevelDebug(2, "Clique:OnClick("..button..", " .. modifiers..")")
    
	if not func then
        self:LevelDebug(3, "Casting from the default set instead.")
		func = Clique.Actions[default][key]
		entry = Clique.db.char[default][key]
	end

    if func then
        func()
		
		-- In case spell failed to apply
		if SpellIsTargeting() then SpellStopTargeting() end

        return true
    else
        --error("Could not find an action for key " .. key)
    end
end

function Clique:CastSpell(spell, unit)
	local restore = nil
	unit = unit or Clique.unit
    
    -- IMPORTANT: If the unit is targettarget or more, then we need to try
    -- to convert it to a friendly unit (to make click-casting work
    -- properly). If this isn't successful, set it up so we restore our 
    -- target
	
	self:LevelDebug(2, "Clique:CastSpell("..tostring(spell)..", "..tostring(unit) .. ")")

    if string.find(unit, "target") and string.len(unit) > 6 then
        local friendly = Clique:GetFriendlyUnit(unit)

        if friendly then
            unit = friendly
        else
			self:LevelDebug(2, "Setting targettarget flag.")
            targettarget = true
        end
    end
    
    -- Lets resolve the targeting.  If this is a hostile target and its
    -- not currently our target, then we will need to target the unit
    if UnitCanAttack("player", unit) then
        if not UnitIsUnit(unit, "target") then
            self:LevelDebug(2, "Changing to hostile target.")
            TargetUnit(unit)
        end

	-- If we're looking at someone else's target, we have to change targets since
    -- ClearTarget() will get rid of the blahtarget unitID entirely.  We only do
	-- this if this is a friendly target (since they will consume the spell)
	elseif targettarget and not UnitCanAttack("player", "target") then
		self:LevelDebug(2, "Changing target due to friendly target.")
		TargetUnit(unit)
    
    -- If the target is a friendly unit, and its not the unit we're casting on
    elseif UnitExists("target") and not UnitCanAttack("player", "target") and not UnitIsUnit(unit, "target") then
        self:LevelDebug(3, "Clearing the target")
        ClearTarget()
        restore = true
	
    elseif UnitExists("target") and self:IsDualSpell(spell) and not UnitIsUnit(unit, "target") then
        self:LevelDebug(3, "Clearing target for this dual spell")
        ClearTarget()
        restore = true
    end

    --self:Print("Clique:CastSpell(%s, %s)", spell, unit)
    --self:Print("Dual Spell: %s, %s", spell, tostring(self:IsDualSpell(spell)))
    
	CastSpellByName(spell)
	
	if SpellIsTargeting() then
        self:LevelDebug(3, "SpellTargetingUnit")
        SpellTargetUnit(unit)
	end
    
    if SpellIsTargeting() then SpellStopTargeting() end
	
	if restore then
        self:LevelDebug(3, "Restoring with TargetLastTarget")
		TargetLastTarget()
	end
end