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.GetCharKnowsTrait(craft, line, trait)
    local known = {}
    local unknown = {}
    local myself = false
    local all = {}
    for i, char in ipairs(LeoAltholic.GetCharacters()) do
        all[char.bio.name] = char.research[craft][line][trait] ~= false
        if char.research[craft][line][trait] ~= false then
            if char.bio.name == GetUnitName("player") then
                myself = true
            end
            table.insert(known, char.bio.name)
        else
            table.insert(unknown, char.bio.name)
        end
    end
    return myself, known, unknown, all
end

function LeoTrainer.IKnowTrait(craft, line, trait)
    local myself = LeoAltholic.GetMyself()
    return myself.research[craft][line][trait] == true
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)
    --GetString("SI_TRADESKILLRESULT", result)
    --d("FAILED " .. event)
end

function LeoTrainer.OnCraftComplete(event, station)

    --local numItemsGained = GetNumLastCraftingResultItemsAndPenalty()
    --local craftFailed = numItemsGained == 0
    --d(craftFailed)

    if LeoTrainer.isCrafting == 0 then return end

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

    -- Player still at station and pressed Craft All
    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 LeoTrainer.IKnowTrait(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()
    LeoTrainerWindowQueuePanelQueueScrollCraftNext:SetState(BSTATE_NORMAL)
    LeoTrainerWindowQueuePanelQueueScrollCraftAll:SetState(BSTATE_NORMAL)
    LeoTrainer.GetPatternIndexes(station)

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

    --[[for line = 1, GetNumSmithingResearchLines(station) do
        local lineName, lineIcon, numTraits = GetSmithingResearchLineInfo(station, line)
        local patternIndex = namesToPatternIndexes[lineName]
        local _, _, matReq = GetSmithingPatternMaterialItemInfo(patternIndex, 1)
        d(line.." "..lineName.." "..patternIndex.." "..matReq)
        for trait = 1, numTraits do
            local traitType = GetSmithingResearchLineTraitInfo(station, line, trait)
            local traitName = GetString('SI_ITEMTRAITTYPE', traitType)
            --local styleId = maxStyle(lineId)
            --local requestResult = {
            --    patternIndex,
            --    1,
            --    matReq,
            --    styleId,
            --    traitType + 1,
            --    LINK_STYLE_BRACKETS
            --}
            --d(GetSmithingPatternResultLink(unpack(requestResult)) .. "{line="..line..", patternIndex="..patternIndex..", materialIndex=1, materialQuantity="..matReq..", itemStyleId="..styleId..", trait="..trait..", traitType="..traitType.."} -- " .. lineName .. " " .. traitName)
        end
    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

    LeoTrainerWindowQueuePanelQueueScrollCraftNext:SetState(BSTATE_DISABLED)
    LeoTrainerWindowQueuePanelQueueScrollCraftAll:SetState(BSTATE_DISABLED)
    LeoTrainer.inStation = 0
    LeoTrainer.queueScroll:RefreshData()
    LeoTrainer.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
    for i, char in ipairs(LeoAltholic.GetCharacters()) 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

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

    SLASH_COMMANDS["/leotrainer"] = function(cmd)
        LeoTrainer:ShowUI()
    end

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

    LeoTrainer.settings = LeoTrainer_Settings:New()
    LeoTrainer.settings:CreatePanel()
    LeoTrainer.launcher = LeoTrainer_Launcher:New()
    LeoTrainer.launcher:SetHidden(false)
    LeoTrainer.CreateUI()
end

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

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

function LeoTrainer.OnAddOnLoaded(event, addonName)
    if addonName == LeoTrainer.name then
        EVENT_MANAGER:UnregisterForEvent(LeoTrainer.name, EVENT_ADD_ON_LOADED)
        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)

        LeoTrainer.log("started.")
    end
end

EVENT_MANAGER:RegisterForEvent(LeoTrainer.name, EVENT_ADD_ON_LOADED, LeoTrainer.OnAddOnLoaded)