--[[
    This code is responsible for managing the overall layout of the timers for any specific module.  
    Basically, it deals with what happens when a timer is added or removed - the actual updates of 
    the timer are left for other code.
    
    Timer Scripts:
        OnTimerEnter(frame, timerdata)
        OnTimerClick(frame, timerdata, button)
        OnGroupEnter(frame, groupdata)
        OnGroupClick(frame, groupdata, button)
        OnDragEnter(frame)
        OnDragClick(frame, button)
--]]

TimerLib = AsheylaLib:NewModule("TimerLib");

local GetTime = GetTime;
local huge = math.huge;
local lower = string.lower;

local timerModule = TimerLibTimer;
local anchorModule = TimerLibAnchor;

TimerLib.anchors = {};
local anchors = TimerLib.anchors;

local defaultSettings, updateTimerSettings;

function updateTimerSettings(self)
    if not self:GetUserSet("timerSettings", "Default Timer") then
        self:Set("timerSettings", "Default Timer", {});
    end
    if not self:GetUserSet("anchorSettings", "Default Anchor") then
        self:Set("anchorSettings", "Default Anchor", {});
    end
    
    for name, anchor in pairs(anchors) do
        anchor:UpdateSettings();
    end
end

function TimerLib:Commands(msg)
    local loaded = LoadAddOn("DoTimer_Options");
    if loaded then
        local panel = (msg == "a" or msg == "anchor") and "TimerLib Anchors" or "TimerLib Timers";
        GUILib:ShowPanel(panel);
    end
end
TimerLib:MakeSlashCmd("/timerlib", "/tl");

-- called by external modules to create timers onscreen.  TimerLib assumes that timerdata is a table consisting of the following elements:
--[[
    name: name of the timer
    time: (optional) when it began
    duration (optional): how long it's running
    texture (optional): texture of the timer
    stack (optional): stack of the timer
--]]
-- groupdata is as follows:
--[[
    name: group name
    icon (optional): raid icon
    level (optional): target level
    uniqueID (optional): a unique identifier for the group
--]]
-- groupdata can also be a string representing the name of the group, if you don't want to make a table for it

function TimerLib:NewTimer(timerdata, groupdata, module, timerSettings, timerPriority, timerHidden, anchorName, groupPriority, groupHidden)
    module = module or self;
    timerdata = timerdata or {duration = 10, time = GetTime()};
    groupdata = groupdata or "Default Group";
    anchorName = anchorName or "Default Anchor";
    local timer = timerModule:NewTimer(module, timerdata, timerSettings, timerPriority, timerHidden);
    local anchor = anchors[anchorName] or anchorModule:NewAnchor(anchorName);
    anchors[anchorName] = anchor;
    local group = anchor:AddTimer(timer, groupdata, groupPriority, groupHidden);
    return timer, group;
end

-- flags the timer for updating on the screen (something about it has changed)
function TimerLib:UpdateTimer(timer, timerdata)
    if not timer then return; end
    if timer.data ~= timerdata then return; end
    timer:UpdateData();
    timer.group:SortTimers();
end

-- flags the group for updating on the screen (something about it has changed)
function TimerLib:UpdateGroup(group, groupdata)
    if not group then return; end
    if group.data ~= groupdata then return; end
    group:UpdateData();
end

-- deletes the timer from the screen
function TimerLib:DeleteTimer(timer, timerdata, destroy)
    if not timer then return; end
    if timer.data ~= timerdata then return; end
    if destroy then
        timer.group:DestroyTimer(timer);
    else
        timer.group:RemoveTimer(timer);
    end
end

function TimerLib:UpdateGroupInfo(group, groupdata, priority, hidden)
    if not group then return; end
    if group.data ~= groupdata then return; end
    local changed = false;
    if group.priority ~= priority then
        group.priority = priority;
        changed = true;
        group.index = nil; -- to force resort
    end
    if group.hidden ~= hidden then
        group.hidden = hidden;
        changed = true;
        group.index = nil; -- to force resort
    end
    if changed then group.anchor:SortGroups(); end
end

function TimerLib:UpdateTimerInfo(timer, timerdata, priority, hidden, settings)
    if not timer then return; end
    if timer.data ~= timerdata then return; end
    local changed = false;
    if timer.priority ~= priority then
        timer.priority = priority;
        changed = true;
        timer.index = nil; -- to force resort
    end
    if timer.hidden ~= hidden then
        timer.hidden = hidden;
        changed = true;
        timer.index = nil; -- to force resort
    end
    if timer.settings ~= settings then
        timer.settings = settings;
        changed = true;
        timer:DrawFrame();
    end
    if changed then
        timer.group:Resize(timer.group:SortTimers());
        timer.group.index = nil;
        timer.group.anchor:SortGroups();
    end
end

function TimerLib:MoveGroup(group, groupdata, anchorName)
    if not group then return; end
    if group.data ~= groupdata then return; end
    if type(groupdata) ~= "table" then return; end
    anchorName = anchorName or "Default Anchor";
    local newAnchor = anchors[anchorName] or anchorModule:NewAnchor(anchorName);
    anchors[anchorName] = newAnchor;
    if newAnchor == group.anchor then return; end
    group.anchor:RemoveGroup(group);
    newAnchor:AddGroup(group);
end

function TimerLib:MoveTimer(timer, timerdata, groupdata, anchorName, groupPriority, groupHidden)
    if not timer then return; end
    if timer.data ~= timerdata then return; end
    --if type(groupdata) ~= "string" then return; end
    --if type(timer.group.data) ~= "string" then return; end
    return timer.group:MoveTimer(timer, group, groupdata, anchorName, groupPriority, groupHidden);
end

function TimerLib:DeleteAllTimers(module)
    for name, anchor in pairs(anchors) do
        for i = #anchor.groups, 1, -1 do
            local group = anchor.groups[i];
            for timer in group:GetTimers() do
                if timer.module == module then
                    group:DestroyTimer(timer);
                end
            end
        end
    end
end

function TimerLib:SetAnchorPositionUpdate(anchorName, bool)
    for name, anchor in pairs(anchors) do
        if name == anchorName then
            anchor.canUpdatePosition = bool;
        end
    end
end

TimerLib.stateMeta = {
    __index = function(t, k)
        return t.state[k];
    end,
    
    __newindex = function(t, k, v)
        t.state[k] = v;
    end,
}

TimerLib.findFrameElement = function(frame, element)
    if not frame[element] then
        frame[element] = getglobal(frame:GetName() .. element);
    end
    
    return frame[element];
end

defaultSettings = {
    anchorSettings = {
        ["Default Anchor"] = {
            locked = false,
            displayNames = true,
            displayIcons = true,
            groupDirection = "down",
            groupSpacing = 10,
            timerDirection = "down", 
            timerSpacing = 5, 
            overflowDirection = "right",
            moveName = true,
            scale = 1,
            timerSortMethod = "Time Remaining (D)",
            groupSortMethod = "Time Added (D)",
            maxNumGroups = 0,
            overflowPoint = 0,
            defaultTimerSetting = "Default Timer",
            standardAlpha = 1,
            combatAlpha = 1,
            mouseoverAlpha = 1,
            anchorsToCursor = false, -- not customizable
            nameTextColor = {
                r = 1.0, g = .82, b = 0,
            },
        },
    },
    
    timerSettings = {
        ["Default Timer"] = {
            timerFormat = "bar",
            alpha = .75,
            scale = 1,
            ghostLength = 3;
            redPoint = 5,
            fadeInLength = 1,
            fadeOutLength = 1,
            timeFormat = "letters",
            tenths = true,
            clickable = true,
            soundAlert = false,
            ghosts = true,
            
            iconDisplayFormat = "outside",
            iconShowCooldown = false,
            iconFlipText = true,
            iconReverseCooldown = true,
            
            barFlipIcon = false,
            barMajorLength = 150,
            barMinorLength = 16,
            barFontHeight = 12,
            barOrientation = "horizontal",
            barShowIcon = true,
            barTexture = "Runes",
            barTextFormat = "%s&:%d",
            barReversed = false,
            barGrow = false,
            barScaling = "standard",
            barAbsScale = 0,
            barTextColor = {
                r = .95, g = .95, b = .95,
            },
            barBackgroundColor = {
                r = .1, g = .6, b = .8,
            },
            
            startColor = {
                r = .2, g = .9, b = .2,
            },
            middleColor = {
                r = .9, g = .9, b = .2,
            },
            finalColor = {
                r = .9, g = .2, b = .2,
            },
            ghostColor = {
                r = .32, g = .56, b = 1.0,
            },
        },
    },
};

TimerLib:AddDefaultSettings(defaultSettings);
TimerLib:AddSettingsUpdateScript(updateTimerSettings);

function test1()
    local f=CreateFrame("Frame") 
    local t=f:CreateTexture() t:SetTexture(1,0,1,.5) t:SetAllPoints(true) 
    f:SetWidth(50) f:SetHeight(50) f:SetPoint("CENTER") 
    f:SetScript("OnEnter", function() DoTimer:Print("enter") f:EnableMouse(false) f:SetScript("OnUpdate", function(self) if not MouseIsOver(self) then DoTimer:Print("leave") self:EnableMouse(true) self:SetScript("OnUpdate", nil) end end) end) 
    f:EnableMouse(true)
end

function r_test()
    DoTimer:RemoveAllGroups();
    local t1 = {name = "target 1"};
    local t2 = {name = "target 2"};
    local s1 = {name = "spell 1"};
    local s2 = {name = "spell 2"};
    local s3 = {name = "spell 3"};
    local s4 = {name = "spell 4"};
    local s5 = {name = "spell 5"};
    local s6 = {name = "spell 6"};
    local f = CreateFrame("Frame");
    f.sent = 0;
    f.elapsed = 0;
    f:SetScript("OnUpdate", function(self, elapsed)
        self.elapsed = self.elapsed + elapsed;
        if self.elapsed >= 0 and self.sent == 0 then
            DoTimer:NewTimer(t1, s1);
            self.sent = 1;
        elseif self.elapsed >= 1 and self.sent == 1 then
            DoTimer:NewTimer(t1, s2);
            self.sent = 2;
        elseif self.elapsed >= 2 and self.sent == 2 then
            DoTimer:NewTimer(t1, s3);
            self.sent = 3;
        elseif self.elapsed >= 3 and self.sent == 3 then
            DoTimer:NewTimer(t2, s4);
            self.sent = 4;
        elseif self.elapsed >= 4 and self.sent == 4 then
            DoTimer:NewTimer(t2, s5);
            self.sent = 5;
        elseif self.elapsed >= 5 and self.sent == 5 then
            DoTimer:NewTimer(t2, s6);
            self.sent = 6;
            self:SetScript("OnUpdate", nil);
        end
    end);
end

function r_test2()
    local timers = {};
    local groups = {};
    for i = 1, 20 do
        local group = {};
        table.insert(groups, group);
        for j = 1, 20 do
            local timer = {duration = 1000};
            table.insert(timers, timer);
            local id = DoTimer:NewTimer(timer, group);
            timer.id = id;
        end
    end
    local f = CreateFrame("Frame");
    f.timers = timers;
    f.groups = groups;
    f.elapsed = 0;
    f:SetScript("OnUpdate", function(self, elapsed)
        self.elapsed = self.elapsed + elapsed;
        if self.elapsed >= 1 then
            self.elapsed = 0;
            if #self.timers > 0 then
                local timer = table.remove(self.timers, math.random(1, #self.timers));
                DoTimer:DeleteTimer(timer.id, timer);
                local id = DoTimer:NewTimer(timer, self.groups[math.random(1, #self.groups)]);
                timer.id = id;
            else
                self:SetScript("OnUpdate", nil);
            end
        end
    end);
end

function r_test3()
    local group = {};
    for i = 1, 7 do
        TimerLib:NewTimer({}, group);
    end
    local group = {};
    for i = 1, 7 do
        TimerLib:NewTimer({}, group);
    end
    local group = {};
    for i = 1, 7 do
        TimerLib:NewTimer({}, group);
    end
end
