require "ISUI/ISInventoryPane"
require "ISUI/ISInventoryPaneContextMenu"
require "ISInventoryTransferAction"
require "RecordedMedia/recorded_media"
-- RAW VERSION
-- ALL PERMISSIONS GRANTED

-- no longer local, please rewrite and hook with caution
KnownAndCollected = {
	allowRender = true, -- use disableRender() if you want to use yours for another UI
	loaded = false,
	reset = false,
	collected = nil,
	collectedMedia = nil,
	textures = {
		collected = getTexture("media/ui/iconUnCollected.png"),
		collectedFolded = getTexture("media/ui/iconUnCollectedFolded.png"),
		unknown = getTexture("media/ui/iconUnKnown.png"),
		unavailable = getTexture("media/ui/iconUnKnownUnavalable.png"),
		media = getTexture("media/ui/iconUnKnownMedia.png"),
		unKnownUnfinished = getTexture("media/ui/iconUnKnownUnfinished.png"),
		unKnownMap = getTexture("media/ui/iconUnKnownMap.png"),
		unKnownMapFolded = getTexture("media/ui/iconUnKnownMapFolded.png")
	},
	offsets = {
		normal = 9, -- vanilla icon offset from left
		stacked = 24, -- vanilla stacked offset from left
	},
	skillMedia = {},
	codesFullParse = false,
	codesOnloadParse = true,
	codesOnloadParsed = false,
	allowSetCollectedOption = true,
	trackEverythingIsCollected = true,
	allowFocusItems = true,
	collectSelfInventoryOnFirstInit = true, -- recursive collect on init
	-- skip next moodles
	moodleCodes = {
		ANG = true, -- Anger
		BOR = true, -- boredom
		FAT = true, -- fatigue
		HUN = true, -- hunger
		STS = true, -- stress
		FEA = true, -- Fear
		PAN = true, -- Panic
		SIC = true, -- Sickness
		PAI = true, -- Pain
		DRU = true, -- Drunkenness
		THI = true, -- thirst
		UHP = true, -- thirst ? Unhappiness !
	},

}

-- Functions
function KnownAndCollected:disableRender() -- disables render
	self.allowRender = false
end
function KnownAndCollected:enableRender()-- disables render
	self.allowRender = true
end
function KnownAndCollected:resetAllCollected()
	self.reset = true
	self.loaded = false
end
function KnownAndCollected.forgetAll(self)
	self.reset = true
end
function KnownAndCollected.setCollected(self,_key,player)
	self.collected[_key] = true
end
function KnownAndCollected.isCollected(self,_key)
	return self.collected[_key] and self.collected[_key] == true
end
function KnownAndCollected.setCollectedMedia(self,id,player)
	self.collectedMedia[id] = true
end
function KnownAndCollected.isCollectedMedia(self,id)
	return self.collectedMedia[id] and self.collectedMedia[id] == true
end

function KnownAndCollected:getModData(player)
	local kAC = KnownAndCollected
	if not player then
		player = self.character
	end
	local modData = player:getModData()
	if modData.knownAndCollected == nil or kAC.reset then
		if kAC.reset and modData.knownAndCollected ~= nil then
			modData.knownAndCollected.knownMaps = nil
			modData.knownAndCollected.collectedMedia = nil
			modData.knownAndCollected.collected = nil
			modData.knownAndCollected.notFirstLoad = nil
			modData.knownAndCollected = nil
			kAC.collected = nil
			kAC.collectedMedia = nil
			kAC.knownMaps = nil
		end
		modData.knownAndCollected = {
			collectedMedia={},
			collected={},
			knownMaps={}
		}
		kAC.collected = modData.knownAndCollected.collected
		kAC.collectedMedia = modData.knownAndCollected.collectedMedia
		kAC.knownMaps = modData.knownAndCollected.knownMaps

		kAC.reset = false
		print('knownAndCollected: reset complete')
	end
	-- update previous version with knownMaps table
	if not modData.knownAndCollected.knownMaps then
		modData.knownAndCollected.knownMaps = {}
		kAC.knownMaps = modData.knownAndCollected.knownMaps
	end
	--
	return modData.knownAndCollected
end

function KnownAndCollected:forgetCollectedByOption(player, itemKeys, itemIsMediaKeys)
	for i, itemKey in ipairs (itemKeys) do
		if itemIsMediaKeys[i] then
			if self.collectedMedia[itemKey] and self.collectedMedia[itemKey] == true then
				self.collectedMedia[itemKey] = nil
				local modData = self:getModData(player)
				modData.collectedMedia[itemKey] = nil
			end 
		else
			if self.collected[itemKey] and self.collected[itemKey] == true then
				self.collected[itemKey] = nil
				local modData = self:getModData(player)
				modData.collected[itemKey] = nil
			end 
		end
	end
end
function KnownAndCollected:setCollectedByOption(player, itemKeys, itemIsMediaKeys)
	for i, itemKey in ipairs (itemKeys) do
		if itemIsMediaKeys[i] then
			self.collectedMedia[itemKey] = true
			local modData = self:getModData(player)
			modData.collectedMedia[itemKey] = true
		else
			self.collected[itemKey] = true
			local modData = self:getModData(player)
			modData.collected[itemKey] = true
		end
	end
end

KnownAndCollected.setCollectedItem = function (self, item, character)
	-- localize
	local kAC = KnownAndCollected
	local isCollectedMedia = kAC.isCollectedMedia
	local isCollected = kAC.isCollected
	local setCollectedMedia = kAC.setCollectedMedia
	local setCollected = kAC.setCollected
	local SkillBook = SkillBook
	--
	local player = character and character or getSpecificPlayer(self.player)
	
	if item:isRecordedMedia() then
		local mediaId = item:getMediaData():getId()
		if not isCollectedMedia(kAC,mediaId) then
			setCollectedMedia(kAC,mediaId,player)
		end
	elseif instanceof(item, 'Literature') then
		local _type = item:getFullType()
		local skillBook = SkillBook[item:getSkillTrained()]
		local recipes = item:getTeachedRecipes()
		if skillBook or recipes then
			if not isCollected(kAC,_type) then
				setCollected(kAC,_type,player)
			end
		end
	elseif item:IsMap() then
		local _type = item:getFullType()
		if not isCollected(kAC,_type) then
			setCollected(kAC,_type,player)
		end
	end
end

function KnownAndCollected:collectInventory (inventory,player)
	local kAC = KnownAndCollected
	local items = inventory:getItems()
	for i = 0, items:size()-1 do
		local item = items:get(i)
		kAC:setCollectedItem(item,player)
		
		if item:getCategory() == "Container" then
			local containerInventory = item:getInventory()
			kAC:collectInventory (containerInventory,player)
		end
	end
end

function KnownAndCollected:collectPlayerInventory (player)
	local kAC = KnownAndCollected
	local inventory = player:getInventory()
	kAC:collectInventory (inventory,player)
end

function KnownAndCollected:init(player)
	local kAC = KnownAndCollected
	if not self.loaded then
		local modData = self:getModData(player)
		if modData.collectedMedia then
			self.collectedMedia = modData.collectedMedia
		end
		if modData.collected then
			self.collected = modData.collected
		end
		if modData.knownMaps then
			self.knownMaps = modData.knownMaps
		end
		if self.collectedMedia ~= nil and self.collected ~= nil and self.knownMaps ~= nil then
			self.loaded = true
		end
		if modData.notFirstLoad == nil then
			if kAC.collectSelfInventoryOnFirstInit then
				kAC:collectPlayerInventory (player)
				print('KnownAndCollected: first time collected')
			end
			modData.notFirstLoad = true
		end
	end
	
end
function KnownAndCollected:isSkillCodeLine (line)
	local kAC = KnownAndCollected
	local moodleCodes = kAC.moodleCodes
	local isSkill = false
	local code = string.match(line, "^%u%u%u+")
	if code ~= nil then
		isSkill = moodleCodes[code] == nil
	end
	
	return isSkill
end
function KnownAndCollected:isSkillMedia (mediaIndex)
	local recMedia = RecMedia
	local kAC = KnownAndCollected
	local skillMedia = kAC.skillMedia
	if skillMedia[mediaIndex] == nil then
		skillMedia[mediaIndex] = {isSkill = false}
		local skill = false
		local mD = RecMedia[mediaIndex]
		if not mD then return false end
		
		for i, l in ipairs(mD.lines) do
			if l.codes == nil or l.codes:len() == 0 then return false end
			if kAC:isSkillCodeLine(l.codes) then
				skill = true
				break
			end
			if kAC.codesFullParse then
				-- try parse multicode from mods separated by ','
				for c in string.gmatch(l.codes, "([^,]+)") do
					if kAC:isSkillCodeLine(c) then
						skill = true
						break
					end
				end
				if skill then break end
			end
		end
		skillMedia[mediaIndex].isSkill = skill
	end
	
	return skillMedia[mediaIndex].isSkill
end

-- known maps
function KnownAndCollected:setKnownMap(_key)
	self.knownMaps[_key] = true
end
function KnownAndCollected:isKnownMap(_key)
	return self.knownMaps[_key] and self.knownMaps[_key] == true
end


-- Injects

-- collected inject
local oldtransferItem = ISInventoryTransferAction.transferItem

ISInventoryTransferAction.transferItem = function (self,item)
	if self:isAlreadyTransferred(item) then
		return
	end
	-- localize
	local oldtransferItem = oldtransferItem
	local kAC = KnownAndCollected
	--
	if kAC.loaded then
		kAC.setCollectedItem(kAC,item, self.character)
	end
	oldtransferItem(self,item)
end
-- open map inject
local oldonCheckMap = ISInventoryPaneContextMenu.onCheckMap
ISInventoryPaneContextMenu.onCheckMap = function(map, player)
	if KnownAndCollected.loaded then
		local playerObj = getSpecificPlayer(player)
		if not luautils.haveToBeTransfered(playerObj, map) then -- skip before open map
			KnownAndCollected:setKnownMap(map:getFullType())
		end
	end
	oldonCheckMap(map, player)
end

--

KnownAndCollected.render = function(self,p)
	-- localize
	local kAC = KnownAndCollected
	local oldRender = kAC.oldRender
	local player = getSpecificPlayer(self.player)
	kAC:init(player) -- your must keep KnownAndCollected.init if your rewrite KnownAndCollected.render
	if kAC.allowRender then
		local isCollectedMedia = KnownAndCollected.isCollectedMedia
		local isCollected = KnownAndCollected.isCollected
		local isKnownMap = KnownAndCollected.isKnownMap
		--
		local recordedMedia = getZomboidRadio():getRecordedMedia()
		local iI = 0
		local auxDXY = math.ceil(20 * self.texScale)
		local auxDXYHalf = math.ceil(auxDXY * 0.5)
		local xK = math.min(self.itemHgt-2,32) + 2 --old = self.column2 - 4


		local MOUSEX = self:getMouseX()
		local MOUSEY = self:getMouseY()
		
		local hotBar = getPlayerHotbar(self.player)
		
		local itemList = self.parent.onCharacter and self.doHideEquipped and self.hideEquippedItemsList or self.itemslist

		for i, stack in ipairs(itemList) do
			local item = stack.items[1]
			local xoff = 0;
			local yoff = 0;
			if self.dragging ~= nil and self.selected[iI+1] ~= nil and self.dragStarted then
				xoff = MOUSEX - self.draggingX
				yoff = MOUSEY - self.draggingY
			end
			local unPlayed = false
			local unCollected = false
			local unCollectedStack = false
			local unKnown = false
			local unKnownUnavailable = false
			local unKnownUnfinished = false
			local isMap = false
			local unKnownMap = false
			local unKnownMapStack = false
			if item:isRecordedMedia() then
				local mediaId = item:getMediaData():getId()
				if not isCollectedMedia(kAC,mediaId) then
					unCollected = true
				end
				if not recordedMedia:hasListenedToAll(player, item:getMediaData()) then
					--unKnown = true
					local isSkillLine = kAC:isSkillMedia (mediaId)
					if isSkillLine then
						unKnown = true
					else
						unPlayed = true
					end
				end
			elseif instanceof(item, 'Literature') then
				local _type = item:getFullType()
				local skillBook = SkillBook[item:getSkillTrained()]
				local recipes = item:getTeachedRecipes()

				if skillBook then
					local maxTrained = item:getMaxLevelTrained()
					local minTrained = item:getLvlSkillTrained()
					
					local playerSkillLevel = player:getPerkLevel(skillBook.perk) + 1
					
					if not isCollected(kAC,_type) then
						unCollected = true
					end
					
					local pages = item:getNumberOfPages()
					local readPages = pages > 0 and player:getAlreadyReadPages(_type) or false
					if readPages and readPages ~= pages and maxTrained >= playerSkillLevel then
						if minTrained > playerSkillLevel then
							unKnownUnavailable = true
						elseif readPages > 0 then
							unKnownUnfinished = true
						else
							unKnown = true
						end
					end

				elseif recipes then
					if not isCollected(kAC,_type) then
						unCollected = true
					end
					if not player:getAlreadyReadBook():contains(_type) or not player:getKnownRecipes():containsAll(recipes) then
						unKnown = true
					end
				end
			elseif item:IsMap() then
				isMap = true
				local _type = item:getFullType()
				if not isCollected(kAC,_type) then
					unCollected = true
				end
				if not isKnownMap(kAC,_type) then
					unKnownMap = true
				end
			end

			local itemName = item:getName()
			if hotBar:isInHotbar(item) then
				itemName = "hotbar:"..itemName
			elseif player:isEquipped(item) then
				itemName = "equipped:"..itemName
			end

			if unCollected or unKnown or unKnownUnfinished or unPlayed or unKnownUnavailable or unKnownMap or unCollectedStack or unKnownMapStack or isMap then
				local yK = iI * self.itemHgt + self.headerHgt + auxDXYHalf + 6
				local yC = iI * self.itemHgt + self.headerHgt + auxDXY + 3
				
				local isStack = #stack.items > 2
				
				if isMap and isStack then
				-- because map use same name and stack together we've to check them all
					local _typeFolded = nil
					for n, map in ipairs(stack.items) do
						if n > 1 then -- skip first?
							local sYstep = (n - 1) * self.itemHgt
							_typeFolded = map:getFullType()
							if not isCollected(kAC,_typeFolded) then
								unCollectedStack = true
								if not self.collapsed[itemName] then
									self:drawTexture(kAC.textures.collected, kAC.offsets.stacked + xoff, yC+yoff+sYstep, 1, 1, 1, 1)
								end
							end
							if not isKnownMap(kAC,_typeFolded) then
								unKnownMapStack = true
								if not self.collapsed[itemName] then
									self:drawTexture(kAC.textures.unKnownMap, kAC.offsets.stacked + xK+xoff, yK+yoff+sYstep, 1, 1, 1, 1)
								end
							end
						end
					end
				end
				
				if unCollected then
					if not isMap or (not isStack or self.collapsed[itemName]) then
						self:drawTexture(kAC.textures.collected, kAC.offsets.normal + xoff, yC+yoff, 1, 1, 1, 1)
					end
				elseif unCollectedStack then
					if self.collapsed[itemName] then
						self:drawTexture(kAC.textures.collectedFolded, kAC.offsets.normal + xoff, yC+yoff, 1, 1, 1, 1)
					end
				end
				if unKnown then
					self:drawTexture(kAC.textures.unknown, kAC.offsets.normal + xK+xoff, yK+yoff, 1, 1, 1, 1)
				elseif unKnownUnfinished then
					self:drawTexture(kAC.textures.unKnownUnfinished, kAC.offsets.normal + xK+xoff, yK+yoff, 1, 1, 1, 1)
				elseif unKnownUnavailable then
					self:drawTexture(kAC.textures.unavailable, kAC.offsets.normal + xK+xoff, yK+yoff, 1, 1, 1, 1)
				elseif unPlayed then
					self:drawTexture(kAC.textures.media, kAC.offsets.normal + xK+xoff, yK+yoff, 1, 1, 1, 1)
				elseif unKnownMap then
					if not isStack or self.collapsed[itemName] then
						self:drawTexture(kAC.textures.unKnownMap, kAC.offsets.normal + xK+xoff, yK+yoff, 1, 1, 1, 1)
					end
				elseif unKnownMapStack then
					if self.collapsed[itemName] then
						self:drawTexture(kAC.textures.unKnownMapFolded, kAC.offsets.normal + xK+xoff, yK+yoff, 1, 1, 1, 1)
					end
				end
			end
			
			iI = iI + 1

			if not self.collapsed[itemName] then
				iI = iI - 1 + #stack.items
			end

		end
	end
	oldRender(self,p)
end

-- Add Options
local function addResetCollectedOption(_player, context, _items)
	-- localize
	local kAC = KnownAndCollected
	if kAC.allowSetCollectedOption then
		local player = getSpecificPlayer(_player)
		kAC:init(player)
		local isCollectedMedia = kAC.isCollectedMedia
		local isCollected = kAC.isCollected
		local SkillBook = SkillBook
		local forgetCollectedByOption = kAC.forgetCollectedByOption
		local setCollectedByOption = kAC.setCollectedByOption
		
		local items = ISInventoryPane.getActualItems(_items)
		local text = {
			collected = getText('IGUI_Set_Collected'),
			uncollected = getText('IGUI_Set_Not_Collected')
		}
		local selectedItems = {}
		local selectedItemsIsMedia = {}
		local selectedItemsUncollected = {}
		local selectedItemsUncollectedIsMedia = {}
		local needCollectedOption = false
		local needUnCollectedOption = false
		local n = 1
		local u = 1
		for i, item in ipairs(items) do
			local addedCollected = false
			local addedUnCollected = false
			local isMedia = false
			local mediaId = nil
			local _type = nil
			if item:isRecordedMedia() then
				mediaId = item:getMediaData():getId()
				if isCollectedMedia(kAC,mediaId) then
					addedCollected = true
				else
					addedUnCollected = true
				end
				isMedia = true
			elseif instanceof(item, 'Literature') then
				local skillBook = SkillBook[item:getSkillTrained()]
				local recipes = item:getTeachedRecipes()
				_type = item:getFullType()
				if skillBook then
					if isCollected(kAC,_type) then
						addedCollected = true
					else
						addedUnCollected = true
					end
				elseif recipes then
					if isCollected(kAC,_type) then
						addedCollected = true
					else
						addedUnCollected = true
					end
				end
			elseif item:IsMap() then
				_type = item:getFullType()
				if isCollected(kAC,_type) then
					addedCollected = true
				else
					addedUnCollected = true
				end
			end
			if addedCollected then
				needCollectedOption = needCollectedOption or addedCollected
				selectedItems[n] = isMedia and mediaId or _type
				selectedItemsIsMedia[n] = isMedia
				n = n + 1
			end
			if addedUnCollected then
				needUnCollectedOption = needUnCollectedOption or addedUnCollected
				selectedItemsUncollected[u] = isMedia and mediaId or _type
				selectedItemsUncollectedIsMedia[u] = isMedia
				u = u + 1
			end
		end
		if needCollectedOption then
			context:addOption(text['uncollected'], kAC, forgetCollectedByOption, player, selectedItems, selectedItemsIsMedia)
		end
		if needUnCollectedOption then
			context:addOption(text['collected'], kAC, setCollectedByOption, player, selectedItemsUncollected, selectedItemsUncollectedIsMedia)
		end
	end
end

Events.OnPreFillInventoryObjectContextMenu.Add(addResetCollectedOption);


function onloadInitKAC ()
	local kAC = KnownAndCollected
	kAC.loaded = false
	local recMedia = RecMedia
	if kAC.codesOnloadParse and not kAC.codesOnloadParsed then
		for i,v in pairs(recMedia) do
			kAC:isSkillMedia(i)
		end
		kAC.codesOnloadParsed = true
	end
	-- set renders
	kAC.oldRender = ISInventoryPane.renderdetails
	ISInventoryPane.renderdetails = kAC.render
end

Events.OnLoad.Add(onloadInitKAC)

-- Mod options 

if ModOptions and ModOptions.getInstance then
	local kAC = KnownAndCollected
	
	local function modOptionsChanged (optionValues)
		local values = optionValues.settings.options
		kAC.allowSetCollectedOption = values.enable_set_collected
		kAC.codesFullParse = values.enable_media_mods_support
		kAC.codesOnloadParse = values.enable_onload_parse_media
	end
	
	local SETTINGS = {

		options_data = {
			--[[
			allow_focus_items = {
				name = "UI_Enable_Focus",
				default = true,
				OnApplyMainMenu = modOptionsChanged,
				OnApplyInGame = modOptionsChanged,
			},]]--
			enable_set_collected = {
				name = "UI_Enable_Set_Collected",
				default = false,
				OnApplyMainMenu = modOptionsChanged,
				OnApplyInGame = modOptionsChanged,
			},
			enable_onload_parse_media = {
				name = "UI_Enable_Onload_Parse_Media",
				default = true,
				OnApplyMainMenu = modOptionsChanged,
				OnApplyInGame = modOptionsChanged,
				tooltip = "UI_Enable_Onload_Parse_Media_tooltip",
			},
			--[[
			track_everything_is_collected = {
				name = "UI_Track_Everything_Is_Collected",
				default = true,
				OnApplyMainMenu = modOptionsChanged,
				OnApplyInGame = modOptionsChanged,
			},]]--
			enable_media_mods_support = {
				name = "UI_Enable_Media_Mods_Support",
				default = false,
				OnApplyMainMenu = modOptionsChanged,
				OnApplyInGame = modOptionsChanged,
				tooltip = "UI_Enable_Media_Mods_Support_tooltip",
			}
		},

		mod_id = 'KnownAndCollected',
		mod_shortname = 'Known And Collected',
		mod_fullname = 'Known And Collected'
	}

	local optionsInstance = ModOptions:getInstance(SETTINGS)
	ModOptions:loadFile()

	Events.OnPreMapLoad.Add(function() modOptionsChanged({ settings = SETTINGS }) end)

end

-- The End

