--[[------------------------------------------------------------------
--SousChef.lua
--Author: Wobin

inspired by ingeniousclown's Research Assistant

------------------------------------------------------------------]]--
SousChef = {}

local LAM = LibStub:GetLibrary("LibAddonMenu-1.0")
local BACKPACK = ZO_PlayerInventoryBackpack
local BANK = ZO_PlayerBankBackpack
local GUILD_BANK = ZO_GuildBankBackpack

local COOKING_RANK_1 = [[SousChef\media\One.dds]]
local COOKING_RANK_2 = [[SousChef\media\Two.dds]]
local COOKING_RANK_3 = [[SousChef\media\Three.dds]]
local COOKING_RANK_4 = [[SousChef\media\Four.dds]]
local COOKING_RANK_5 = [[SousChef\media\Five.dds]]
local COOKING_RANK_6 = [[SousChef\media\Six.dds]]
local COOKING_FLAVOUR = [[SousChef\media\Flavour.dds]]
local COOKING_SPICE =   [[SousChef\media\Spice.dds]]
local COOKING_RANK_1B = [[SousChef\media\One_flat.dds]]
local COOKING_RANK_2B = [[SousChef\media\Two_flat.dds]]
local COOKING_RANK_3B = [[SousChef\media\Three_flat.dds]]
local COOKING_RANK_4B = [[SousChef\media\Four_flat.dds]]
local COOKING_RANK_5B = [[SousChef\media\Five_flat.dds]]
local COOKING_RANK_6B = [[SousChef\media\Six_flat.dds]]
local COOKING_FLAVOURB = [[SousChef\media\flavour_flat.dds]]
local COOKING_SPICEB =   [[SousChef\media\Spice_Flat.dds]]

local COOKING = { COOKING_RANK_1, COOKING_RANK_2, COOKING_RANK_3, COOKING_RANK_4, COOKING_RANK_5, COOKING_RANK_6, COOKING_FLAVOUR, COOKING_SPICE }
local COOKINGB = { COOKING_RANK_1B, COOKING_RANK_2B, COOKING_RANK_3B, COOKING_RANK_4B, COOKING_RANK_5B, COOKING_RANK_6B, COOKING_FLAVOURB, COOKING_SPICEB }

local CANLEARN = [[/esoui/art/loot/loot_finesseitem.dds]]
local containerHooks = { INVENTORY_BACKPACK, INVENTORY_BANK, INVENTORY_GUILD_BANK }
local itemQuality = { ITEM_QUALITY_MAGIC = { 0, 0, 1 }, ITEM_QUALITY_NORMAL = {1,1,1}, ITEM_QUALITY_ARCANE = {1, 0, 1}}
SousChef.Pantry = {}
SousChef.Cookbook = {}
SousChef.ReverseCookbook = {}
SousChef.settings = nil
SousChef.slotLines = {}
SousChef.hookedFunctions = {}
SousChef.hookedDataFunction = nil

local function GetItemID(link)
	return tonumber(string.match(string.match(link, "%d+:"), "%d+"))
end

function EndsWith(String,End)
   return End=='' or string.sub(String,-string.len(End))==End
end

function StartsWith(String,Start)
    return Start=='' or string.sub(String, 1, string.len(Start))==Start
end

local languageElements = {"de ", "à ", "la ", }
local separators = {"%^(%a+)", "-", " " }

local function StripLanguageIdentifiers(entry)
    for _,v in pairs(languageElements) do
        entry = entry:gsub(v, "")
    end
    return entry
end

local function Compress(entry)
    for _,v in pairs(separators) do
        entry = entry:gsub(v, "")
    end
    return entry
end

local function CleanString(entry)
    if SousChef.settings.experimentalMatch then
        entry = StripLanguageIdentifiers(entry)
    end
    return Compress(entry):lower()
end

local function MatchesRecipe(entry)
    return CleanString(entry):find(CleanString(GetString(SI_ITEMTYPE29)))
end

local function TableKeyConcat(t)
    local tt = {}
    for k in pairs(t) do tt[#tt+1]=k end
    return table.concat(tt, ", ")
end

local function MatchInCookbook(name)
    name = CleanString(name)
    for recipe,known in pairs(SousChef.Cookbook) do
        if StartsWith(name,recipe) or EndsWith(name, recipe) then
            local difference =  (#recipe + #CleanString(GetString(SI_ITEMTYPE29)) - #name)
            if  difference < 3 and difference > -3 then
                return known
            end
        end
    end
	return nil
end

local function MatchInGlobalCookbook(name)
    name = CleanString(name)
    for recipe,known in pairs(SousChef.settings.Cookbook) do
        if StartsWith(name,recipe) or EndsWith(name, recipe) then
            local difference =  (#recipe + #CleanString(GetString(SI_ITEMTYPE29)) - #name)
            if  difference < 3 and difference > - 3 then
                return known
            end
        end
    end
    return nil
end

 rowClicked = {}

local function AddDetails(row)
	if not row.dataEntry or not row.dataEntry.data or rowClicked[row] then return false end
	local rowInfo = row.dataEntry.data
	local bagId = rowInfo.bagId
	local slotIndex = rowInfo.slotIndex

	if MatchesRecipe(rowInfo.name) then
        local gmatch = MatchInGlobalCookbook(rowInfo.name)
        if gmatch then
            ItemTooltip:AddLine("")
            ItemTooltip:AddLine("Known by ", "ZoFontWinH5", 1,1,1, BOTTOM, MODIFY_TEXT_TYPE_UPPERCASE)
            ItemTooltip:AddLine(TableKeyConcat(gmatch))
            rowClicked[row] = true
            return
        end
    end

	if ((GetItemCraftingInfo(bagId, slotIndex)) ~= CRAFTING_TYPE_PROVISIONING) then	return false end

	local usableIngredient = SousChef.ReverseCookbook[GetItemID(GetItemLink(bagId, slotIndex))]
	if SousChef.settings.showAltKnowledge then usableIngredient = SousChef.settings.ReverseCookbook[GetItemID(GetItemLink(bagId, slotIndex))] end
	if usableIngredient then
		ItemTooltip:AddLine("Used in:", "ZoFontWinH5", 1,1,1, BOTTOM, MODIFY_TEXT_TYPE_UPPERCASE)
	    ItemTooltip:AddLine(table.concat(usableIngredient, ", "))
		rowClicked[row] = true
	end
	return false
end

local function getIcon(row)
	local rankIcon = SousChef.slotLines[row:GetName()]
	if(not rankIcon) then
		rankIcon =  WINDOW_MANAGER:CreateControl(row:GetName() .. "SousChef", row, CT_TEXTURE)
        SousChef.slotLines[row:GetName()] = rankIcon
		ZO_PreHookHandler(row, "OnMouseDown", AddDetails)
		ZO_PreHookHandler(row, "OnMouseExit", function(self) rowClicked[self] = nil return false end )
	end
	return rankIcon
end

local rowHandler = {}

local function AddRankToSlot(row)
    local slot = row.dataEntry.data
    local bagId = slot.bagId
    local slotIndex = slot.slotIndex
	local rankIcon = getIcon(row)

	-- Allow for ingeniousclown's Inventory Grid View
	if row:GetWidth() - row:GetHeight() < 5 then	-- if we're mostly square
		rankIcon:SetDimensions(20,20)
		rankIcon:SetAnchor(TOPLEFT, row, TOPLEFT, 2)
	else
		rankIcon:SetDimensions(30, 30)
		rankIcon:SetAnchor(CENTER, row, CENTER, 200)
	end

	rankIcon:SetHidden(true)

	if ((GetItemCraftingInfo(bagId, slotIndex)) == CRAFTING_TYPE_PROVISIONING) then
		local texture = SousChef.Pantry[GetItemID(GetItemLink(bagId, slotIndex))]
		if SousChef.settings.showAltKnowledge then texture = SousChef.settings.Pantry[GetItemID(GetItemLink(bagId, slotIndex))] end
		if texture then
			rankIcon:SetColor(SousChef.settings.colour[1], SousChef.settings.colour[2], SousChef.settings.colour[3])
			rankIcon:SetHidden(false)
			if SousChef.settings.boldIcon then
				rankIcon:SetTexture(COOKINGB[texture])
			else
				rankIcon:SetTexture(COOKING[texture])
			end

		end
    else
        if SousChef.settings.processRecipes then
            if GetItemType(bagId, slotIndex) == ITEMTYPE_RECIPE then
                local match = MatchInCookbook(slot.name)
                local gmatch = MatchInGlobalCookbook(slot.name)
                if (match and SousChef.settings.checkKnown == "known") or
                   (not match and SousChef.settings.checkKnown == "unknown")then
                    rankIcon:SetTexture(CANLEARN)
                    rankIcon:SetHidden(false)
                    if not match and gmatch and SousChef.settings.checkKnown == "unknown" and SousChef.settings.markAlt then
                        rankIcon:SetColor(1,1,1,0.2)
                    else
                        rankIcon:SetColor(1,1,1,1)
                    end
                end
            end
		end
	end
end

local function AddTradingSlot(row, result)
	local rankIcon = getIcon(row)

	rankIcon:SetHidden(true)
    if SousChef.settings.processRecipes then
        if MatchesRecipe(result.name) then
            local match = MatchInCookbook(result.name)
            local gmatch = MatchInGlobalCookbook(result.name)
            if (match and SousChef.settings.checkKnown == "known") or
               (not match and SousChef.settings.checkKnown == "unknown")then
                rankIcon:SetDimensions(30, 30)
                rankIcon:SetAnchor(CENTER, row, CENTER, 230)
                rankIcon:SetTexture(CANLEARN)
                rankIcon:SetHidden(false)
                if not match and gmatch and SousChef.settings.checkKnown == "unknown" and SousChef.settings.markAlt then
                    rankIcon:SetColor(1,1,1,0.2)
                else
                    rankIcon:SetColor(1,1,1,1)
                end
            end
        end
    end
end

local function AddRecipe(Cookbook, link)
	for _,v in pairs(Cookbook) do
		if v == link then return end
	end
	table.insert(Cookbook, link)
end

local function ParseRecipes()
	local lists = GetNumRecipeLists()
	for listIndex = 1, lists do
		local name, count = GetRecipeListInfo(listIndex)
		for recipeIndex = 1, count do
			if GetRecipeInfo(listIndex, recipeIndex) then
				-- Store the recipes known
				local recipeName = CleanString((GetRecipeResultItemInfo(listIndex, recipeIndex)))
                SousChef.Cookbook[recipeName] = true
				if not SousChef.settings.Cookbook[recipeName] then
                    SousChef.settings.Cookbook[recipeName] = {}
				end
                SousChef.settings.Cookbook[recipeName][GetUnitName("player")] = true
				local _, _, ingredientCount, level, _, specialType = GetRecipeInfo(listIndex, recipeIndex)
				for ingredientIndex = 1, ingredientCount do
					local link = GetItemID(GetRecipeIngredientItemLink(listIndex, recipeIndex, ingredientIndex, LINK_STYLE_NORMAL))
					-- Store the fact that the ingredient is used
                    if ingredientIndex < 3 then
                        SousChef.Pantry[link] = math.max(level, SousChef.Pantry[link] or 0)
                        SousChef.settings.Pantry[link] = math.max(level, SousChef.Pantry[link] or 0)
                    else
                        SousChef.Pantry[link] = specialType == PROVISIONER_SPECIAL_INGREDIENT_TYPE_FLAVORING and 7 or 8
                        SousChef.settings.Pantry[link] = specialType == PROVISIONER_SPECIAL_INGREDIENT_TYPE_FLAVORING and 7 or 8
                    end
					-- Store the recipe it's used in
					if not SousChef.ReverseCookbook[link] then SousChef.ReverseCookbook[link] = {} end
					AddRecipe(SousChef.ReverseCookbook[link], zo_strformat(SI_TOOLTIP_ITEM_NAME, GetRecipeResultItemLink(listIndex, recipeIndex, LINK_STYLE_BRACKETS)))
					-- Store the global reference
					if not SousChef.settings.ReverseCookbook[link] then SousChef.settings.ReverseCookbook[link] = {} end
					AddRecipe(SousChef.settings.ReverseCookbook[link], zo_strformat(SI_TOOLTIP_ITEM_NAME, GetRecipeResultItemLink(listIndex, recipeIndex, LINK_STYLE_BRACKETS)))
				end
			end

		end
	end
end

local function SousChefCreateSettings()
    d("Creating menu")
	local panel = LAM:CreateControlPanel("SousChefMenu", "Sous Chef")

	LAM:AddHeader(panel, "SousChef_General", "Settings")

    LAM:AddCheckbox(panel, "processRecipes", "Have Sous Chef check for recipes", "Non English clients may want to untick this if the experimental matching isn't sufficient",
                        function() return SousChef.settings.processRecipes end,
                        function(value)
                            SousChef.settings.processRecipes = not SousChef.settings.processRecipes
                            if SousChef.settings.processRecipes then
                                ZO_Options_SetOptionInactive(markLearnt)
                                ZO_Options_SetOptionInactive(markAltKnows)
                            else
                                ZO_Options_SetOptionActive(markLearnt)
                                ZO_Options_SetOptionActive(markAltKnows)
                            end
                        end)
  	LAM:AddDropdown(panel, "markLearnt", "Mark if recipes are ",
						"How do you want Sous Chef to indicate your knowledge of a recipe?",
						{"known", "unknown"}, function() return SousChef.settings.checkKnown end,
						function(valueString) SousChef.settings.checkKnown = valueString end)
	LAM:AddCheckbox(panel, "markAltKnows", "Alternate Character Check", "Indicate if an alt knows the recipe on unknown recipes. Will only work if the above setting is set to 'unknown'",
						function() return SousChef.settings.markAlt end,
						function(value) SousChef.settings.markAlt = not SousChef.settings.markAlt end)
	LAM:AddCheckbox(panel, "showAltKnows", "Alternate Character Recipe Knowledge", "Show rank indicators on alts for all recipe knowledge of all alternate characters",
						function() return SousChef.settings.showAltKnowledge end,
						function(value) SousChef.settings.showAltKnowledge = not SousChef.settings.showAltKnowledge end)
	LAM:AddCheckbox(panel, "useBold", "Use bolder icons", "Swap out rank icon to a more flat display",
						function() return SousChef.settings.boldIcon end,
						function(value) SousChef.settings.boldIcon = not SousChef.settings.boldIcon end)
	LAM:AddColorPicker(panel, "setColour", "Indicator colour",
						"Allows you to set the colour of the indicator",
						function() return SousChef.settings.colour[1], SousChef.settings.colour[2], SousChef.settings.colour[3] end,
						function(r,g,b) SousChef.settings.colour[1] = r; SousChef.settings.colour[2] = g; SousChef.settings.colour[3] = b end)
    LAM:AddCheckbox(panel, "experimental", "Use the experimental recipe matcher", [[Currently Sous Chef cannot match reliably in other languages. This will attempt to match as best it can by stripping out common prepositions so that the recipe and result match better.

    (NOTE: please file a bug report if you find recipes that don't match the results exactly, with the recipe/result names and/or screenshots so I can update this dictionary)]],
                        function() return SousChef.settings.experimentalMatch end,
                        function(value) SousChef.settings.experimentalMatch = not SousChef.settings.experimentalMatch end)
end


local function HookRecipeTreeFunction()
    -- Hook the provisioning panel
    if not SousChef.hookedProvisioningFunction then
        local ref = PROVISIONER.recipeTree.templateInfo.ZO_ProvisionerNavigationEntry
        if ref then
            SousChef.hookedProvisioningFunction = ref.setupFunction
            ref.setupFunction =
            function(...)
                local node, control, data, open, userRequested, enabled = ...
                SousChef.hookedProvisioningFunction(...)

            end
        end
    end
end

local function HookRecipeTree(...)
    local eventId, craftingTable = ...
    if craftingTable ~= CRAFTING_TYPE_PROVISIONING then return end
    zo_callLater(HookRecipeTreeFunction, 1000)
end


local function HookTrading(...)
	if SousChef.hookedDataFunction then return end
    SousChef.hookedDataFunction = ZO_TradingHouseItemPaneSearchResults.dataTypes[1].setupCallback
	ZO_TradingHouseItemPaneSearchResults.dataTypes[1].setupCallback = function(...)
		local row, data = ...
        SousChef.hookedDataFunction(...)
		AddTradingSlot(row, data)
	end
end
local function SousChef_Loaded(eventCode, addOnName)

	if(addOnName ~= "SousChef") then
        return
    end

	local defaults = {
		watching = true,
		checkKnown = "unknown",
		markAlt = false,
		colour = {1, 1, 1},
		Cookbook = {},
		Pantry = {},
		ReverseCookbook = {},
		showAltKnowledge = false,
		boldIcon = false,
        experimentalMatch = false,
        processRecipes = true
	}

    SousChef.settings = ZO_SavedVars:NewAccountWide("SousChef_Settings", 8, nil, defaults)

	local function tablelength(T)
		local count = 0
		for _ in pairs(T) do count = count + 1 end
		return count
	end

	SLASH_COMMANDS['/scstats'] = function()
		d("Number of recipes known: ".. tablelength(SousChef.settings.Cookbook))
		d("Number of ingredients tracked: "..tablelength(SousChef.settings.Pantry))
	end

	SousChefCreateSettings()

	ParseRecipes()



	-- Now we want to hook into the function that sets the details on the inventory slot
	for _,v in pairs(PLAYER_INVENTORY.inventories) do
		local listView = v.listView
		if listView and listView.dataTypes and listView.dataTypes[1] then
            SousChef.hookedFunctions = listView.dataTypes[1].setupCallback

			listView.dataTypes[1].setupCallback =
				function(rowControl, slot)
                    SousChef.hookedFunctions(rowControl, slot)
					AddRankToSlot(rowControl)
				end
		end
    end

	ZO_ScrollList_RefreshVisible(BACKPACK)
	ZO_ScrollList_RefreshVisible(BANK)
	ZO_ScrollList_RefreshVisible(GUILD_BANK)

	EVENT_MANAGER:RegisterForEvent("SousChefTrading", EVENT_TRADING_HOUSE_RESPONSE_RECEIVED, HookTrading)
	EVENT_MANAGER:RegisterForEvent("SousChefLearnt", EVENT_RECIPE_LEARNED, ParseRecipes)

    EVENT_MANAGER:RegisterForEvent("SousChefProvi", EVENT_CRAFTING_STATION_INTERACT, HookRecipeTree)

end

local function SousChef_Initialized()
	EVENT_MANAGER:RegisterForEvent("SousChefLoaded", EVENT_ADD_ON_LOADED, SousChef_Loaded)
end



SousChef_Initialized()