local gps = LibGPS3

local currentTopic = ""
local currentQuestGiver = ""
local currentSpeaker = ""

local function GetSavedVariable(typ, zone, subzone, name, cat)
	if not ConversationsQQ_SavedVariables_v15[typ] then
		ConversationsQQ_SavedVariables_v15[typ] = {}
	end
	
	if not ConversationsQQ_SavedVariables_v15[typ][zone] then
		ConversationsQQ_SavedVariables_v15[typ][zone] = {}
	end
	
	if typ == "quest" then
		return ConversationsQQ_SavedVariables_v15[typ][zone]
	end
	
	if not ConversationsQQ_SavedVariables_v15[typ][zone][subzone] then
		ConversationsQQ_SavedVariables_v15[typ][zone][subzone] = {}
	end
	
	if not name then
		return ConversationsQQ_SavedVariables_v15[typ][zone][subzone]
	end
	
	if not ConversationsQQ_SavedVariables_v15[typ][zone][subzone][name] then
		ConversationsQQ_SavedVariables_v15[typ][zone][subzone][name] = {}
	end
	
	if not ConversationsQQ_SavedVariables_v15[typ][zone][subzone][name][cat] then
		ConversationsQQ_SavedVariables_v15[typ][zone][subzone][name][cat] = {}
	end
	
	return ConversationsQQ_SavedVariables_v15[typ][zone][subzone][name][cat]
end

local function GetSavedVariableQuest(journalIndex, questName, questLocation)
	local sv = GetSavedVariable("quest", questLocation)
	
	if not sv[questName] then
		sv[questName] = {}
	end
	
	if not sv[questName]["steps"] then
		sv[questName]["steps"] = {}
	end
	
	if not sv[questName]["info"] then
		sv[questName]["info"] = {}
	end
	
	if not sv[questName]["info"]["description"] then
		sv[questName]["info"]["description"] = {}
	end
	
	return sv[questName]
end

local function split(s, delimiter)
    result = {}
    for match in (s..delimiter):gmatch("(.-)"..delimiter) do
        table.insert(result, match)
    end
    return result
end

local function multi(str)
	
	local check = string.find(str, "\n[\-]+\n")
	--local check2 = string.find(str, "\|u")
	local temp = str
	
	--if check2 ~= nil then
	--	temp = split(temp, "\|u")[1]
	--end
	
	if check ~= nil then
		temp = string.gsub(temp, "^.+\n[\-]+\n", "")
	end
	
	return temp
end

local function zoneStrip(str)
    local check = string.find(str, "\n")
	local temp = str
	
	if check ~= nil then
		temp = split(temp, "\n")[1]
	end
	
	return temp
end

local function SaveConversationOptions(sv, optionCount)
	for i=1, optionCount do
		local optionText = multi(GetChatterOption(i))
		
		if not sv[optionText] then
			sv[optionText] = {}
		end
	end
end

local function SaveConversationOptionsLinks(sv3, link, optionCount)
	
	link = multi(link)
	
	if not sv3[link] then
		sv3[link] = {}
	end

	for i=1, optionCount do
		local optionText = multi(GetChatterOption(i))
		if not sv3[link][optionText] then
			sv3[link][optionText] = ""
		end
	end
end

local function SaveQuestItemsInfo(journalIndex, questName, questLocation, typ, itemName)	
	if itemName == nil or itemName == "" or string.len(itemName) < 1 then
		return false
	end

	local sv = GetSavedVariableQuest(journalIndex, questName, questLocation)
	
	if sv["info"][typ] == nil then
		sv["info"][typ] = {}
	end
	
	local sv2 = sv["info"][typ]

	if #sv2 ~= 0 then 
		for i = 1, #sv2 do
			if sv2[i] == itemName then
				return false
			end
		end
	end
	
	sv2[#sv2 + 1] = itemName
end

local function ConversationUpdated(event, body, optionCount)
	currentSpeaker = zo_strformat(SI_UNIT_NAME, GetRawUnitName("interact"))

	local typ = "npc"
	
	if SetMapToPlayerLocation() ~= SET_MAP_RESULT_CURRENT_MAP_UNCHANGED  then
		CALLBACK_MANAGER:FireCallbacks("OnWorldMapChanged")
	end
	
	local _, _, zoneMapIndex = gps:LocalToGlobal(GetMapPlayerPosition())
	
	local zone = GetMapNameByIndex(zoneMapIndex)
	local subzone = GetMapName()
	if subzone == nil then
		subzone = zo_strformat("<<1>>", GetUnitZone("interact"))
	end
	
	subzone = zoneStrip(subzone)
	
	local name = zo_strformat(SI_UNIT_NAME, GetRawUnitName("interact"))--.."|"..GetUnitGender("interact")
	
	local sv = GetSavedVariable(typ, zone, subzone, name, "topics")
	local sv2 = GetSavedVariable(typ, zone, subzone, name, "greetings")
	local sv3 = GetSavedVariable(typ, zone, subzone, name, "links")
	
	body = multi(body)
	
	SaveConversationOptionsLinks(sv3, body, optionCount)
	
	if #currentTopic == 0 then
		SaveConversationOptions(sv, optionCount)
		
		for i=1, #sv2 do
			if sv2[i] == body then
				return
			end
		end
		
		table.insert(sv2, body)
	else
		if not sv[currentTopic] then
			sv[currentTopic] = {}
		end
		
		sv[currentTopic][body] = ""
		
		SaveConversationOptions(sv, optionCount)
	end
end

local isSubtitleStart = true
local lastSubtitle = ""
local lastSubtitleTimestamp = 0
local subtitlesBlock = 1

local function subtitlesTiming()
	if lastSubtitle == "" then
		isSubtitleStart = true
		return false
	end
	
	local CHARACTERS_PER_SECOND = 10
	local MIN_DISPLAY_LENGTH_SECONDS = 3
    local MAX_DISPLAY_LENGTH_SECONDS = 12
	
	local currentTimestamp = GetSecondsSinceMidnight()
	local timestampDiff = 0
	local beforeMidnight = ZO_ONE_DAY_IN_SECONDS - lastSubtitleTimestamp
	
	local textLength = ZoUTF8StringLength(lastSubtitle)
    local displayLength = zo_clamp(textLength / CHARACTERS_PER_SECOND, MIN_DISPLAY_LENGTH_SECONDS, MAX_DISPLAY_LENGTH_SECONDS) + 5
	
	if beforeMidnight < 30 and currentTimestamp < 30 then
		timestampDiff = beforeMidnight + currentTimestamp
	else
		timestampDiff = currentTimestamp - lastSubtitleTimestamp
	end
	
	if timestampDiff < 0 or timestampDiff > displayLength then
		isSubtitleStart = true
		lastSubtitle = ""
		lastSubtitleTimestamp = 0
	end
	
end

local function ConversationSubtitleOnUpdate(eventCode, msgType, from, text) --ConversationSubtitleOnUpdate
	if msgType ~= CHAT_CHANNEL_MONSTER_EMOTE and 
		msgType ~= CHAT_CHANNEL_MONSTER_SAY and
		msgType ~= CHAT_CHANNEL_MONSTER_WHISPER and
		msgType ~= CHAT_CHANNEL_MONSTER_YELL then return end
	local typ = "subtitles"
	
	text = multi(text)
	
	if SetMapToPlayerLocation() ~= SET_MAP_RESULT_CURRENT_MAP_UNCHANGED  then
		CALLBACK_MANAGER:FireCallbacks("OnWorldMapChanged")
	end
	
	local _, _, zoneMapIndex = gps:LocalToGlobal(GetMapPlayerPosition())
	
	local zone = GetMapNameByIndex(zoneMapIndex)
	local subzone = GetMapName()
	if subzone == nil then
		subzone = zo_strformat("<<1>>", GetUnitZone("player"))
	end
	subzone = zoneStrip(subzone)
	local name = zo_strformat("<<C:1>>", from)
	
	if isSubtitleStart == false then
		subtitlesTiming()
	end
	
	local sv = GetSavedVariable(typ, zone, subzone)
	
	if isSubtitleStart == true then
	
		if #sv == 0 then
			subtitlesBlock = 1
		else
			for i=1, #sv do
				if sv[i] ~= nil then
					if sv[i][1] ~= nil then
						if sv[i][1]["text"] == text and sv[i][1]["name"] == name then
							subtitlesBlock = i
							lastSubtitle = text
							lastSubtitleTimestamp = GetSecondsSinceMidnight()
							isSubtitleStart = false
							return false
						end
					end
				end
			end
			
			subtitlesBlock = #sv + 1
		end
		
		sv[subtitlesBlock] = {}
		sv[subtitlesBlock][1] = {}
		
		lastSubtitle = text
		lastSubtitleTimestamp = GetSecondsSinceMidnight()
		isSubtitleStart = false
		
		sv[subtitlesBlock][1]["name"] = name
		sv[subtitlesBlock][1]["text"] = text
		
		return false
	end
	
	if not sv[subtitlesBlock] then
		sv[subtitlesBlock] = {}
	end
	
	for i=1, #sv[subtitlesBlock] do
		if sv[subtitlesBlock][i]["text"] ~= nil and sv[subtitlesBlock][i]["name"] ~= nil then
			if sv[subtitlesBlock][i]["text"] == text and sv[subtitlesBlock][i]["name"] == name then
				lastSubtitle = text
				lastSubtitleTimestamp = GetSecondsSinceMidnight()
				return false
			end
		end
	end
	
	newIndex = #sv[subtitlesBlock] + 1
	
	lastSubtitle = text
	lastSubtitleTimestamp = GetSecondsSinceMidnight()
	
	sv[subtitlesBlock][newIndex] = {}
	sv[subtitlesBlock][newIndex]["name"] = name
	sv[subtitlesBlock][newIndex]["text"] = text
end

local function ConversationQuestComplete(_, journalQuestIndex) --ConversationQuestComplete
	local typ = "npc"
	local _, endDialog = GetJournalQuestEnding(journalQuestIndex)
	
	endDialog = multi(endDialog)
	
	if SetMapToPlayerLocation() ~= SET_MAP_RESULT_CURRENT_MAP_UNCHANGED  then
		CALLBACK_MANAGER:FireCallbacks("OnWorldMapChanged")
	end
	
	local _, _, zoneMapIndex = gps:LocalToGlobal(GetMapPlayerPosition())
	
	local zone = GetMapNameByIndex(zoneMapIndex)
	local subzone = GetMapName()
	if subzone == nil then
		subzone = zo_strformat("<<1>>", GetUnitZone("interact"))
	end
	subzone = zoneStrip(subzone)
	local name = zo_strformat(SI_UNIT_NAME, GetRawUnitName("interact"))--.."|"..GetUnitGender("interact")
	
	local questName = GetJournalQuestInfo(journalQuestIndex)
	SaveQuestItemsInfo(journalQuestIndex, questName, GetJournalQuestLocationInfo(journalQuestIndex), "finisher", name)
	
	local sv = GetSavedVariable(typ, zone, subzone, name, "topics")
	
	if not sv[currentTopic] then
		sv[currentTopic] = {}
	end
	
	sv[currentTopic][endDialog] = ""
end

local function IterateSteps(journalIndex, questName, questLocation, parentCategory)
	local sv = GetSavedVariableQuest(journalIndex, questName, questLocation)
	local sv2 = sv["steps"]
	
	if parentCategory == nil or parentCategory == "" or string.len(parentCategory) < 1 then
		if #sv2 == 0 then
			sv2[#sv2 + 1] = {}
			sv2[#sv2]["description"] = parentCategory
		end
		return sv2[#sv2]	
	end
	
	if #sv2 ~= 0 then 
		for i = 1, #sv2 do
			if sv2[i]["description"] == parentCategory then
				return sv2[i]
			end
		end
	end
	
	sv2[#sv2 + 1] = {}
	sv2[#sv2]["description"] = parentCategory
	
	return sv2[#sv2]
end

local function AddInvolved(journalIndex, questName, questLocation)
	local svSpeaker = GetSavedVariableQuest(journalIndex, questName, questLocation)
	local svSpeaker2 = svSpeaker["info"]
	
	if currentSpeaker ~= "" then
		if not svSpeaker2["involved"] then
			svSpeaker2["involved"] = {}
		end
		
		if #svSpeaker2["involved"] ~= 0 then 
			for i = 1, #svSpeaker2["involved"] do
				if svSpeaker2["involved"][i] == currentSpeaker then
					return false
				end
			end
		end
		
		svSpeaker2["involved"][#svSpeaker2["involved"] + 1] = currentSpeaker
	end	
end

local function IterateGoals(journalIndex, questName, questLocation, parentCategory, category, goal)
	
	if goal == "TRACKER GOAL TEXT" or goal == nil or goal == "" or string.len(goal) < 1 then
		return false
	end
	
	local checkGoal = string.match(goal, "^(.+): %d+ / %d+$")
	
	if checkGoal ~= nil then
		goal = checkGoal
	end

	local sv = IterateSteps(journalIndex, questName, questLocation, parentCategory)
	
	if not sv["goals"] then
		sv["goals"] = {}
	end
	
	if not sv["goals"][category] then
		sv["goals"][category] = {}
	end
	
	local sv2 = sv["goals"][category]

	if #sv2 ~= 0 then 
		for i = 1, #sv2 do
			if sv2[i] == goal then
				return false
			end
		end
	end
	
	sv2[#sv2 + 1] = goal
end

local function WriteGoals(journalIndex, questName, questLocation, stepIndex, conditionCount, parentCategory, category)

	for conditionIndex=1, conditionCount do
		local conditionText = GetJournalQuestConditionInfo(journalIndex, stepIndex, conditionIndex)
		if conditionText ~= "TRACKER GOAL TEXT" and conditionText ~= nil and conditionText ~= "" and string.len(conditionText) >= 1 then
			IterateGoals(journalIndex, questName, questLocation, parentCategory, category, conditionText)
		end
	end	
end

local function SaveQuestInfo(journalIndex, questName, questLocation)	
	local sv = GetSavedVariableQuest(journalIndex, questName, questLocation)	
	local sv2 = sv["info"]["description"]
	
	local _, backgroundText = GetJournalQuestInfo(journalIndex)

	if #sv2 ~= 0 then 
		for i = 1, #sv2 do
			if sv2[i] == backgroundText then
				return false
			end
		end
	end
	
	sv2[#sv2 + 1] = backgroundText
end

local function SaveGoals(journalIndex, questName, questLocation)
	SaveQuestInfo(journalIndex, questName, questLocation)

	for stepIndex = 1, GetJournalQuestNumSteps(journalIndex) do
		local stepText, visibility, stepType, stepOverrideText, conditionCount = GetJournalQuestStepInfo(journalIndex, stepIndex)
		
		if conditionCount == 0 then
			IterateGoals(journalIndex, questName, questLocation, stepText, "main", stepOverrideText)
			return false
		end
		
		if stepIndex == 1 then
			WriteGoals(journalIndex, questName, questLocation, stepIndex, conditionCount, stepText, "main")
		else
			if visibility == QUEST_STEP_VISIBILITY_HINT then
				WriteGoals(journalIndex, questName, questLocation, stepIndex, conditionCount, stepText, "hints")
			elseif visibility == QUEST_STEP_VISIBILITY_OPTIONAL then
				WriteGoals(journalIndex, questName, questLocation, stepIndex, conditionCount, stepText, "optional")
			else
				WriteGoals(journalIndex, questName, questLocation, stepIndex, conditionCount, stepText, "hidden")
			end
		end
    end
end

local function SaveQuestItems(journalIndex, questName, questLocation)
	local numSteps = GetJournalQuestNumSteps(journalIndex)
	local numTools = GetQuestToolCount(journalIndex)
		
	for stepIndex = 1, numSteps do
		local numConditions = GetJournalQuestNumConditions(journalIndex, stepIndex)
		
		for condIndex = 1, numConditions do
			local _, _, name, _ = GetQuestItemInfo(journalIndex, stepIndex, condIndex)
			SaveQuestItemsInfo(journalIndex, questName, questLocation, "items", name)
		end
	end
	
	for toolIndex = 1, numTools do
		local _, _, _, name, _ = GetQuestToolInfo(journalIndex, toolIndex)
		SaveQuestItemsInfo(journalIndex, questName, questLocation, "items", name)
	end
end

local function ConversationQuestAdd(_, journalIndex, questName, ...)
	SaveGoals(journalIndex, questName, GetJournalQuestLocationInfo(journalIndex))
	SaveQuestItems(journalIndex, questName, GetJournalQuestLocationInfo(journalIndex))
	SaveQuestItemsInfo(journalIndex, questName, GetJournalQuestLocationInfo(journalIndex), "starter", currentQuestGiver)
	currentQuestGiver = nil
end

local function ConversationQuest(_, journalIndex, questName, ...)
	SaveGoals(journalIndex, questName, GetJournalQuestLocationInfo(journalIndex))
	SaveQuestItems(journalIndex, questName, GetJournalQuestLocationInfo(journalIndex))
	AddInvolved(journalIndex, questName, GetJournalQuestLocationInfo(journalIndex))
end

local function ConversationBooks(_, bookTitle, _, _, _, bookId)
	local typ = "books"
	
	if SetMapToPlayerLocation() ~= SET_MAP_RESULT_CURRENT_MAP_UNCHANGED  then
		CALLBACK_MANAGER:FireCallbacks("OnWorldMapChanged")
	end
	
	local _, _, zoneMapIndex = gps:LocalToGlobal(GetMapPlayerPosition())
	
	local zone = GetMapNameByIndex(zoneMapIndex)
	local subzone = GetMapName()
	if subzone == nil then
		subzone = zo_strformat("<<1>>", GetUnitZone("player"))
	end
	
	subzone = zoneStrip(subzone)
	local sv = GetSavedVariable(typ, zone, subzone)
	
	if sv[bookId] == nil then
		sv[bookId] = bookTitle
	end
end

local function ConversationsInitialize( event, addonName )
	if addonName ~= "ConversationsQQ" then
		return
	end
	
	if not ConversationsQQ_SavedVariables_v15 then
		ConversationsQQ_SavedVariables_v15 = {}
	end
	
	local SelectChatterOptionOrg = SelectChatterOption
	SelectChatterOption = function(...)
		local optionIndex = ...
		currentTopic = multi(GetChatterOption(optionIndex))
		return SelectChatterOptionOrg(...)
	end

	local GetOfferedQuestInfoOrg = GetOfferedQuestInfo
	GetOfferedQuestInfo = function(...)
		local ret = { GetOfferedQuestInfoOrg(...) }
		
		local ret1 = ret[1]
		ret1 = multi(ret1)
		local ret2 = ret[2]
		ret2 = multi(ret2)
		
		--if #currentTopic == 0 then
		--	return unpack( ret )
		--end
		local typ = "npc"
		
		if SetMapToPlayerLocation() ~= SET_MAP_RESULT_CURRENT_MAP_UNCHANGED  then
			CALLBACK_MANAGER:FireCallbacks("OnWorldMapChanged")
		end
		
		local _, _, zoneMapIndex = gps:LocalToGlobal(GetMapPlayerPosition())
	
		local zone = GetMapNameByIndex(zoneMapIndex)
		local subzone = GetMapName()
		if subzone == nil then
			subzone = zo_strformat("<<1>>", GetUnitZone("interact"))
		end
		
		subzone = zoneStrip(subzone)
		local name = zo_strformat(SI_UNIT_NAME, GetRawUnitName("interact"))--.."|"..GetUnitGender("interact")
		
		local sv = GetSavedVariable(typ, zone, subzone, name, "topics")
		local sv3 = GetSavedVariable(typ, zone, subzone, name, "links")
		
		if not sv3[ret1] then
			sv3[ret1] = {}
		end
		
		if not sv3[ret1][ret2] then
			sv3[ret1][ret2] = ""
		end
		
		if not sv[currentTopic] or #sv[currentTopic] == 0 then
			sv[currentTopic] = {}
			sv[currentTopic][ret1] = ""
		end
		
		if not sv[ret2] or #sv[ret2] == 0 then
			sv[ret2] = {}
		end
		
		currentTopic = ret2
		return unpack( ret )
	end
	
	local GetChatterGreetingOrg = GetChatterGreeting
	GetChatterGreeting = function(...)
		currentTopic = ""
		local typ = "npc"
		
		if SetMapToPlayerLocation() ~= SET_MAP_RESULT_CURRENT_MAP_UNCHANGED  then
			CALLBACK_MANAGER:FireCallbacks("OnWorldMapChanged")
		end
		
		local _, _, zoneMapIndex = gps:LocalToGlobal(GetMapPlayerPosition())
	
		local zone = GetMapNameByIndex(zoneMapIndex)
		local subzone = GetMapName()
		if subzone == nil then
			subzone = zo_strformat("<<1>>", GetUnitZone("interact"))
		end
		subzone = zoneStrip(subzone)
		
		local name = zo_strformat(SI_UNIT_NAME, GetRawUnitName("interact"))--.."|"..GetUnitGender("interact")
		
		local sv = GetSavedVariable(typ, zone, subzone, name, "greetings")
		local sv3 = GetSavedVariable(typ, zone, subzone, name, "links")
		
		local ret = { GetChatterGreetingOrg(...) }
		local ret1 = ret[1]
		ret1 = multi(ret1)
		
		local sv2 = GetSavedVariable(typ, zone, subzone, name, "topics")
		optionCount = GetChatterOptionCount()
		
		SaveConversationOptions(sv2, optionCount)
		SaveConversationOptionsLinks(sv3, ret1, optionCount)
		
		for i=1, #sv do
			if sv[i] == ret1 then
				return unpack( ret )
			end
		end
		
		table.insert(sv, ret1)
		return unpack(ret)
	end
end

EVENT_MANAGER:RegisterForEvent("ConversationsQQ", EVENT_ADD_ON_LOADED, ConversationsInitialize)
EVENT_MANAGER:RegisterForEvent("ConversationsQQ", EVENT_CONVERSATION_UPDATED, ConversationUpdated)
EVENT_MANAGER:RegisterForEvent("ConversationsQQ", EVENT_CHAT_MESSAGE_CHANNEL, ConversationSubtitleOnUpdate)
EVENT_MANAGER:RegisterForEvent("ConversationsQQ", EVENT_QUEST_COMPLETE_DIALOG, ConversationQuestComplete)
EVENT_MANAGER:RegisterForEvent("ConversationsQQ", EVENT_CHATTER_BEGIN, function(...) currentSpeaker = zo_strformat(SI_UNIT_NAME, GetRawUnitName("interact")) end)
EVENT_MANAGER:RegisterForEvent("ConversationsQQ", EVENT_CHATTER_END, function(...) currentTopic = ""
zo_callLater(function()
	currentSpeaker = ""
end, 1000) end)
--Quests
EVENT_MANAGER:RegisterForEvent("ConversationsQQ", EVENT_QUEST_ADDED, ConversationQuestAdd)
EVENT_MANAGER:RegisterForEvent("ConversationsQQ", EVENT_QUEST_ADVANCED, ConversationQuest)
EVENT_MANAGER:RegisterForEvent("ConversationsQQ", EVENT_QUEST_CONDITION_COUNTER_CHANGED, ConversationQuest)

EVENT_MANAGER:RegisterForEvent("ConversationsQQ", EVENT_QUEST_OFFERED, function(...) currentQuestGiver = zo_strformat(SI_UNIT_NAME, GetRawUnitName("interact")) end)

EVENT_MANAGER:RegisterForEvent("ConversationsQQ", EVENT_SHOW_BOOK, ConversationBooks)