local function copy(obj, seen)
    if type(obj) ~= 'table' then return obj end
    if seen and seen[obj] then return seen[obj] end
    local s = seen or {}
    local res = setmetatable({}, getmetatable(obj))
    s[obj] = res
    for k, v in pairs(obj) do res[copy(k, s)] = copy(v, s) end
    return res
end

function LeoTrainer.maxStyle(piece)
    local maxStyleId = -1
    local maxQty = 0
    for _,i in ipairs(LeoTrainer.const.racialStyles) do
        if IsSmithingStyleKnown(i, piece) == true then
            local qty = GetCurrentSmithingStyleItemCount(i)
            if qty > maxQty then
                maxStyleId = i
                maxQty = qty
            end
        end
    end
    return maxStyleId
end

function LeoTrainer.GetPatternIndexes(craft)
    LeoTrainer.namesToPatternIndexes[craft] = {
        ["names"] = {},
        ["lines"] = {}
    }
    for patternIndex = 1, GetNumSmithingPatterns() do
        local _, name = GetSmithingPatternInfo(patternIndex)
        LeoTrainer.namesToPatternIndexes[craft]["names"][name] = patternIndex
    end

    for line = 1, GetNumSmithingResearchLines(craft) do
        local lineName, lineIcon, numTraits = GetSmithingResearchLineInfo(craft, line)
        LeoTrainer.namesToPatternIndexes[craft]["lines"][line] = LeoTrainer.namesToPatternIndexes[craft]["names"][lineName]
    end
end

function LeoTrainer.OnCraftFailed(event, station)
end

function LeoTrainer.OnCraftComplete(event, station)

    if LeoTrainer.isCrafting == 0 then return end

    LeoTrainer.RemoveFromQueue(LeoTrainer.isCrafting)
    LeoTrainer.isCrafting = 0

    if GetCraftingInteractionType() ~= CRAFTING_TYPE_INVALID and LeoTrainer.continueCrating == true then
        zo_callLater(function() LeoTrainer.CraftNext() end, 200)
    end
end

function LeoTrainer.CraftItem(queueIndex, data)

    LeoTrainer.log("Crafting " .. data.itemLink .. " ("..data.researchName..") ...")

    if IsPerformingCraftProcess() == true then
        LeoTrainer.log("Still crafting ...")
        return false
    end

    data.patternIndex = LeoTrainer.namesToPatternIndexes[data.craft]["lines"][data.line]

    local matName, _, matReq = GetSmithingPatternMaterialItemInfo(data.patternIndex, data.materialIndex)
    data.materialQuantity = matReq

    local curMats = GetCurrentSmithingMaterialItemCount(data.patternIndex, data.materialIndex)
    if curMats < data.materialQuantity then
        local diff = data.materialQuantity - curMats
        LeoTrainer.log("Not enough " .. matName .. ". Need " .. diff .. " more.")
        LeoTrainer.continueCrating = false
        return false
    end

    data.itemStyleId = LeoTrainer.maxStyle(data.line)

    if data.itemStyleId == -1 then
        LeoTrainer.log("Not enough known style material.")
        LeoTrainer.continueCrating = false
        return false
    end

    local request = {
        data.patternIndex,
        data.materialIndex,
        data.materialQuantity,
        data.itemStyleId,
        data.traitIndex + 1,
        data.useUniversalStyleItem
    }

    LeoTrainer.isCrafting = queueIndex
    CraftSmithingItem(unpack(request))
    return true
end

function LeoTrainer.CraftNext()
    local list = LeoTrainer.GetQueue()
    for index, data in ipairs(list) do
        if data.craft == LeoTrainer.inStation and LeoAltholic.CharKnowsTrait(data.craft, data.line, data.trait) == true then
            LeoTrainer.CraftItem(index, data)
            return
        end
    end
    LeoTrainer.log("Nothing more to craft at this station.")
    LeoTrainer.isCrafting = 0
    LeoTrainer.continueCrating = false
end

function LeoTrainer.stationEnter(eventcode, station)
    if station ~= CRAFTING_TYPE_BLACKSMITHING and station ~= CRAFTING_TYPE_WOODWORKING and
            station ~= CRAFTING_TYPE_CLOTHIER and station ~= CRAFTING_TYPE_JEWELRYCRAFTING then return end

    LeoTrainer.inStation = station
    LeoTrainer.queueScroll:RefreshData()
    LeoTrainerWindowQueuePanelQueueScrollCraftAll:SetState(BSTATE_NORMAL)
    LeoTrainer.GetPatternIndexes(station)

    local namesToPatternIndexes = {}
    for patternIndex = 1, GetNumSmithingPatterns() do
        local _, name = GetSmithingPatternInfo(patternIndex)
        namesToPatternIndexes[name] = patternIndex
    end
end

function LeoTrainer.stationExit(eventcode, station)
    if station ~= CRAFTING_TYPE_BLACKSMITHING and station ~= CRAFTING_TYPE_WOODWORKING and
            station ~= CRAFTING_TYPE_CLOTHIER and station ~= CRAFTING_TYPE_JEWELRYCRAFTING then return end

    LeoTrainerWindowQueuePanelQueueScrollCraftAll:SetState(BSTATE_DISABLED)
    LeoTrainer.inStation = 0
    LeoTrainer.queueScroll:RefreshData()
    LeoTrainerUI.HideUI()
end

function LeoTrainer.isTrackingSkill(charName, craftId)
    return LeoTrainer.savedVariables.trackedTraits[charName][craftId]
end

function LeoTrainer.setTrackingSkill(charName, craftId, tracking)
    LeoTrainer.savedVariables.trackedTraits[charName][craftId] = tracking
end

function LeoTrainer.canFillSlotWithSkill(charName, craftId)
    return LeoTrainer.savedVariables.fillSlot[charName][craftId]
end

function LeoTrainer.setFillSlotWithSkill(charName, craftId, tracking)
    LeoTrainer.savedVariables.fillSlot[charName][craftId] = tracking
end

function LeoTrainer.Initialize()

    LeoTrainer.savedVariables = ZO_SavedVars:NewAccountWide("LeoTrainerSavedVariables", 1, nil, nil, GetWorldName())
    if LeoTrainer.savedVariables.trackedTraits == nil then
        LeoTrainer.savedVariables.trackedTraits = {}
    end
    if not LeoTrainer.savedVariables.queue then
        LeoTrainer.savedVariables.queue = {}
    end
    if not LeoTrainer.savedVariables.fillSlot then
        LeoTrainer.savedVariables.fillSlot = {}
    end
    if not LeoTrainer.savedVariables.trainNirnhoned then
        LeoTrainer.savedVariables.trainNirnhoned = false
    end
    if not LeoTrainer.savedVariables.researchItems then
        LeoTrainer.savedVariables.researchItems = false
    end
    if not LeoTrainer.savedVariables.onlyResearchFCO then
        LeoTrainer.savedVariables.onlyResearchFCO = false
    end
    if not LeoTrainer.savedVariables.defaultTrainer then
        LeoTrainer.savedVariables.defaultTrainer = "Anyone"
    end
    for i, char in ipairs(LeoAltholic.ExportCharacters()) do
        if LeoTrainer.savedVariables.trackedTraits[char.bio.name] == nil then
            LeoTrainer.savedVariables.trackedTraits[char.bio.name] = {}
        end
        if not LeoTrainer.savedVariables.fillSlot[char.bio.name] then
            LeoTrainer.savedVariables.fillSlot[char.bio.name] = {}
        end
        for _, craftId in pairs(LeoAltholic.craftResearch) do
            LeoTrainer.savedVariables.trackedTraits[char.bio.name][craftId] = LeoTrainer.savedVariables.trackedTraits[char.bio.name][craftId] or false
            LeoTrainer.savedVariables.fillSlot[char.bio.name][craftId] = LeoTrainer.savedVariables.fillSlot[char.bio.name][craftId] or false
        end
    end

    local LibFeedback = LibStub:GetLibrary("LibFeedback")
    local showButton, feedbackWindow = LibFeedback:initializeFeedbackWindow(LeoTrainer,
            LeoTrainer.name,LeoTrainerWindow, "@LeandroSilva",
            {TOPRIGHT, LeoTrainerWindow, TOPRIGHT,-50,3},
            {0,1000,10000,"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Y9KM4PZU2UZ6A"},
            "If you found a bug, have a request or a suggestion, or simply wish to donate, send a mail.")
    LeoTrainer.feedback = feedbackWindow
    LeoTrainer.feedback:SetDrawLayer(DL_OVERLAY)
    LeoTrainer.feedback:SetDrawTier(DT_MEDIUM)

    LeoTrainerWindowTitle:SetText(LeoTrainer.displayName .. " v" .. LeoTrainer.version)

    SLASH_COMMANDS["/leotrainer"] = function(cmd)
        LeoTrainerUI:ToggleUI()
    end

    if GetDisplayName() == "@LeandroSilva" then
        SLASH_COMMANDS["/rr"] = function(cmd)
            ReloadUI()
        end
    end

    LeoTrainer.knowledge = {}
    LeoTrainer.missingKnowledge = {}
    local charList = LeoAltholic.ExportCharacters()
    for _, char in pairs(charList) do
        for _,craft in pairs(LeoAltholic.craftResearch) do
            if LeoTrainer.knowledge[craft] == nil then LeoTrainer.knowledge[craft] = {} end
            if LeoTrainer.missingKnowledge[craft] == nil then LeoTrainer.missingKnowledge[craft] = {} end
            for line = 1, GetNumSmithingResearchLines(craft) do
                if LeoTrainer.knowledge[craft][line] == nil then LeoTrainer.knowledge[craft][line] = {} end
                if LeoTrainer.missingKnowledge[craft][line] == nil then LeoTrainer.missingKnowledge[craft][line] = {} end
                local lineName, lineIcon, numTraits = GetSmithingResearchLineInfo(craft, line)
                for trait = 1, numTraits do
                    if LeoTrainer.knowledge[craft][line][trait] == nil then LeoTrainer.knowledge[craft][line][trait] = {} end
                    if LeoTrainer.missingKnowledge[craft][line][trait] == nil then LeoTrainer.missingKnowledge[craft][line][trait] = {} end
                    if char.research.done[craft][line][trait] == true then
                        table.insert(LeoTrainer.knowledge[craft][line][trait], char.bio.name)
                    else
                        table.insert(LeoTrainer.missingKnowledge[craft][line][trait], char.bio.name)
                    end
                end
            end
        end
    end

    LeoTrainer.settings = LeoTrainer_Settings:New()
    LeoTrainer.settings:CreatePanel()

    LeoTrainer.launcher = LeoTrainer_Launcher:New()
    LeoTrainer.launcher:SetHidden(false)

    LeoTrainerUI.RestorePosition()
    LeoTrainer.CreateUI()
    LeoTrainer.UpdateUI()
end

function LeoTrainer.log(message)
    d(LeoTrainer.chatPrefix .. message)
end

local function OnSettingsControlsCreated(panel)
    LeoTrainer_Settings:OnSettingsControlsCreated(panel)
end

local function onNewMovementInUIMode(eventCode)
    if not LeoTrainerWindow:IsHidden() then LeoTrainerUI:CloseUI() end
end

local function onChampionPerksSceneStateChange(oldState,newState)
    if newState == SCENE_SHOWING then
        if not LeoTrainerWindow:IsHidden() then LeoTrainerUI:CloseUI() end
    end
end

local function onLeoAltholicInitialized()
    CALLBACK_MANAGER:UnregisterCallback("LeoAltholicInitialized", onLeoAltholicInitialized)
    SCENE_MANAGER:RegisterTopLevel(LeoTrainerWindow, false)

    LeoTrainer.Initialize()

    EVENT_MANAGER:RegisterForEvent(LeoTrainer.name, EVENT_CRAFTING_STATION_INTERACT, LeoTrainer.stationEnter)
    EVENT_MANAGER:RegisterForEvent(LeoTrainer.name, EVENT_END_CRAFTING_STATION_INTERACT, LeoTrainer.stationExit)
    EVENT_MANAGER:RegisterForEvent(LeoTrainer.name, EVENT_CRAFT_COMPLETED, LeoTrainer.OnCraftComplete)
    EVENT_MANAGER:RegisterForEvent(LeoTrainer.name, EVENT_CRAFT_FAILED, LeoTrainer.OnCraftFailed)
    CALLBACK_MANAGER:RegisterCallback("LAM-PanelControlsCreated", OnSettingsControlsCreated)
    EVENT_MANAGER:RegisterForEvent(LeoTrainer.name, EVENT_NEW_MOVEMENT_IN_UI_MODE, onNewMovementInUIMode)
    CHAMPION_PERKS_SCENE:RegisterCallback('StateChange', onChampionPerksSceneStateChange)

    LeoTrainer.log("started.")
end

function LeoTrainer.OnAddOnLoaded(event, addonName)
    if addonName == LeoTrainer.name then
        EVENT_MANAGER:UnregisterForEvent(LeoTrainer.name, EVENT_ADD_ON_LOADED)
        onLeoAltholicInitialized()
    end
end

CALLBACK_MANAGER:RegisterCallback("LeoAltholicInitialized", onLeoAltholicInitialized)
--EVENT_MANAGER:RegisterForEvent(LeoTrainer.name, EVENT_ADD_ON_LOADED, LeoTrainer.OnAddOnLoaded)