-----------------------------------
--Controller for the Credits UI
-----------------------------------

local AsyncUtil = require("lib_async")

local Class = require("lib_class")

local CreditsController = Class()

--- Called by the class constructor
--- @return nil
function CreditsController:init()
	--hide the mouse
	io.setCursorHidden(true)

	self.Document = nil --- @type Document the RML document
	self.ScrollPosition = 0 --- @type number the current scroll position
	self.CampaignFilename = nil --- @type string the filename of the campaign
	self.TotalMusicTime = 0 --- @type number the cumulative time of all the credits music in milliseconds
	self.TextRcssColorClass = nil --- @type string the color of the text
	self.CampaignTitle = nil --- @type string the title of the campaign
	self.CreditsFooter = nil --- @type string the footer of the credits
	self.ScrollFadeHeight = 0 --- @type number the scroll position at which the credits fade out
	self.CreditsElement = nil --- @type Element the actual credits element
	self.ScrollTotalHeight = 0 --- @type number the total height of the credits
	self.ScrollIncrement = 0 --- @type number the scroll increment per frame
	self.Track_Lengths = {} --- @type number[] the length of each track in milliseconds

	self.MusicInfo = {
		Music_Files = {}, --- @type string[] the list of music files
		CurrentTrack = nil, --- @type number the current track being played
		MusicHandle = nil, --- @type audio_stream the handle for the current music stream
		Waiting = false --- @type boolean whether the system is waiting to play the next track
	}

	self.FadeInData = {
		Active = false, --- @type boolean whether the fade in is active
		Current = 255, --- @type number the current alpha value of the black overlay
		Timing = 2 --- @type number the time it takes to fade in
	}

	self.FadeOutData = {
		Active = false, --- @type boolean whether the fade out is active
		Current = 255, --- @type number the current alpha value of the black overlay
		Timing = 2 --- @type number the time it takes to fade in
	}

	--- @type credits_image_memory
	self.CreditsImage = {
		X1 = 0,
		Y1 = 0,
		X2 = 0,
		Y2 = 0,
		Index = 0,
		Alpha = 0,
		FadeAmount = 0,
		Timer = 0,
		FadeTimer = 0,
		imageFile1 = nil,
		imageFile2 = nil
	}
end

--- Called by the RML document
--- @param document Document
function CreditsController:initialize(document)
    self.Document = document
	ScpuiSystem.CreditsContext = self --- Reference for cross frame access

	ba.setDiscordPresence("Watching Credits")
	self:hideHelp(self.Document:GetElementById("escape_help"))

	if not ScpuiSystem.ForceCredits then
		ui.MainHall.stopAmbientSound()
	end

	---Load background choice
    self.Document:GetElementById("main_background"):SetClass(ScpuiSystem:getBackgroundClass(), true)

	---Load the desired font size from the save file
	self.Document:GetElementById("main_background"):SetClass(("base_font" .. ScpuiSystem:getFontPixelSize()), true)

	ui.TechRoom.buildCredits()

	if ScpuiSystem.ForceCredits then
		self.CampaignFilename = ba.getCurrentPlayer():getCampaignFilename()
	else
		self.CampaignFilename = nil
	end

	--THIS IS TEMPORARY FOR TESTING
	--self.CampaignFilename = "bta3_m"

	if self.CampaignFilename == "bta1_m" then
		self.TextRcssColorClass = "cred_orange"
		self.CampaignTitle = ba.XSTR("Slaves of Chaos", 36)
		self.MusicInfo.Music_Files = {"ThisTooShallPass.ogg"}
	elseif self.CampaignFilename == "bta2_m" then
		self.TextRcssColorClass = "cred_green"
		self.CampaignTitle = ba.XSTR("The Growing Silence", 38)
		--self.musicInfo.music_files = {"Legacy.ogg"}
		self.MusicInfo.Music_Files = {"RiseUp.ogg", "Terminus.ogg"}
	elseif self.CampaignFilename == "bta3_m" then
		self.TextRcssColorClass = "cred_purple"
		self.CampaignTitle = ba.XSTR("Edge of a Legacy", 11544)
		--self.MusicInfo.Music_Files = {"Goliath.ogg"}
		self.MusicInfo.Music_Files = {"Legacy.ogg"}
	else
		ba.print("Credits couldn't find a valid campaign!\n")
		self.TextRcssColorClass = "cred_red"
		self.CampaignTitle = ba.XSTR("", -1)
		self.MusicInfo.Music_Files = {"FaithExt.ogg"}
	end

	self:initMusic()
	self:beginFadeIn()

	local wrapper_el = self.Document:GetElementById("credits_text")

	local complete_credits = ui.TechRoom.Credits.Complete

	--Split credit items into left and right sides
	complete_credits = complete_credits:gsub(
		"([^~\n]*[^~\n%s])(%s*~%s*)([^\n]*)",
		"<span class=\"left\">%1</span>%2<span class=\"right\">%3</span>"
	)

	--Replace new lines with break tags
	complete_credits = complete_credits:gsub("\n","<br></br>")

	self:loadCreditsFooter()
	--Add the opening divs to the top and the footer at the bottom
	complete_credits = "<div class=\"group\">" .. complete_credits .. self.CreditsFooter

	--Now add the group divs throughout
	complete_credits = complete_credits:gsub("<br></br>", "<br></br><div style=\"clear: both;\"></div></div><div class=\"group\">")

	--Add the closing divs to the bottom
	complete_credits = complete_credits .. "<div style=\"clear: both;\"></div></div>"

	--Replace /// with break tags
	complete_credits = complete_credits:gsub("///", "<br></br>")

	--Do some coloring!
	local replacement = "<div class=\"group\"><span class=\"italic " .. self.TextRcssColorClass .. "\">%-"
	complete_credits = complete_credits:gsub("<div class=\"group\">%-", replacement)
	complete_credits = complete_credits:gsub("%-<br></br>", "%-</span><br></br>")
	local split = "<span class=\"" .. self.TextRcssColorClass .. " dash\"> - </span>"
	complete_credits = complete_credits:gsub(" ~ ", split)

	--Now create the SCP built-in credits colors
	local scp_header = "<span class=\"" .. self.TextRcssColorClass .. "\">----------------------------------<br></br></span>SOURCE CODE PROJECT STAFF<br></br><span class=\"" .. self.TextRcssColorClass .. "\">----------------------------------</span>"
	local scp_project_leader = "<span class=\"italic " .. self.TextRcssColorClass .. "\">%-Project Leader%-</span>"
	local scp_senior_advisors = "<span class=\"italic " .. self.TextRcssColorClass .. "\">%-Senior Advisors%-</span>"
	local scp_programmers = "<span class=\"italic " .. self.TextRcssColorClass .. "\">%-Programmers%-</span>"
	local scp_web_support = "<span class=\"italic " .. self.TextRcssColorClass .. "\">%-Web Support%-</span>"
	local scp_special_thanks = "<span class=\"italic " .. self.TextRcssColorClass .. "\">%-Special thanks to%-</span>"
	local scp_very_special_thanks = "<span class=\"italic " .. self.TextRcssColorClass .. "\">%-Very special thanks to%-</span>"
	local scp_technologies = "<span class=\"italic " .. self.TextRcssColorClass .. "\">%-FS2_Open makes use of the following technologies%-</span>"
	local scp_link_hard_light = "<span class =\"cred_link\">http://www%.hard%-light%.net/</span>"
	local scp_link_scp_indie_games = "<span class =\"cred_link\">http://scp%.indiegames%.us/</span>"

	--Now actually do the SCP credits replacements
	complete_credits = complete_credits:gsub("SOURCE CODE PROJECT STAFF:", scp_header)
	complete_credits = complete_credits:gsub("Project Leader:", scp_project_leader)
	complete_credits = complete_credits:gsub("Senior Advisors:", scp_senior_advisors)
	complete_credits = complete_credits:gsub("Programmers:", scp_programmers)
	complete_credits = complete_credits:gsub("Web Support:", scp_web_support)
	complete_credits = complete_credits:gsub("Special thanks to:", scp_special_thanks)
	complete_credits = complete_credits:gsub("Very special thanks to:", scp_very_special_thanks)
	complete_credits = complete_credits:gsub("FS2_Open makes use of the following technologies:", scp_technologies)
	complete_credits = complete_credits:gsub("http://www%.hard%-light%.net/", scp_link_hard_light)
	complete_credits = complete_credits:gsub("http://scp%.indiegames%.us/", scp_link_scp_indie_games)

	--We need to calculate how much empty space to add before and after the credits
	--so that we can cleanly loop the text. Get the height of the div, the height of
	--a line, and do some math. Add that number of line breaks before and after!
	local credits_height = wrapper_el.offset_height
	local line_height = self.Document:GetElementById("text_size_temp").offset_height
	local num_breaks = (math.ceil((credits_height / line_height) + ((4 - ScpuiSystem:getFontPixelSize()) * 1.3)))
	local credits_bookend = ""

	--This is the point at which we stop scrolling to show the logo then fade out
	self.ScrollFadeHeight = line_height * num_breaks

	while(num_breaks > 0) do
		credits_bookend = credits_bookend .. "<br></br>"
		num_breaks = num_breaks - 1
	end

	self.Document:GetElementById("text_size_temp"):SetClass("hidden", true)

	--Append new lines to the top and bottom of Credits so we can loop it later seamlessly
	complete_credits = credits_bookend .. complete_credits .. credits_bookend
	 --Add a few more breaks to the top to make sure it starts offscreen after the fade in
	complete_credits = "<br></br><br></br><br></br><br></br><br></br><br></br><br></br><br></br><br></br><br></br>" .. complete_credits

	--Now create a div to put all the text in
	local text_el = self.Document:CreateElement("div")
	text_el.id = "credits_text_inner"
	text_el.inner_rml = complete_credits
	wrapper_el:AppendChild(text_el)

	--Add the BtA logo to the scroll
	local title_el = self.Document:CreateElement("div")
	title_el.id = "credits_title_footer"
	title_el.inner_rml = ba.XSTR("Between the Ashes", 169)
	title_el:SetClass("h3", true)
	wrapper_el:AppendChild(title_el)

	local subtitle_el = self.Document:CreateElement("div")
	subtitle_el.id = "credits_subtitle_footer"
	subtitle_el.inner_rml = self.CampaignTitle
	subtitle_el:SetClass("h1", true)
	wrapper_el:AppendChild(subtitle_el)

	self.CreditsElement = wrapper_el

	--Total scroll height
	self.ScrollTotalHeight = self.CreditsElement.scroll_height - self.ScrollFadeHeight

	self.ScrollIncrement = (self.ScrollTotalHeight / self.TotalMusicTime) * 17 -- I don't know why this magic number works! #MATH YALL
	ba.print("CREDITS INCREMENT RATE IS " ..  self.ScrollIncrement .. "\n")

	self:scrollCredits()

	local image_el = self.Document:GetElementById("credits_image")
	local image_x1 = image_el.offset_left + image_el.parent_node.offset_left
	local image_y1 = image_el.offset_top + image_el.parent_node.offset_top

	self.CreditsImage = {
		X1 = image_x1,
		Y1 = image_y1,
		X2 = image_x1 + image_el.offset_width,
		Y2 = image_y1 + image_el.offset_height,
		Index = ui.TechRoom.Credits.StartIndex,
		Alpha = 0,
		FadeAmount = 0.01 / ui.TechRoom.Credits.FadeTime,
		Timer = ui.TechRoom.Credits.DisplayTime,
		FadeTimer = ui.TechRoom.Credits.FadeTime,
		imageFile1 = nil,
		imageFile2 = nil
	}

	--self:chooseImage()
	--self:timeImages()

end

--- Load the credits footer from the footer table
--- @return nil
function CreditsController:loadCreditsFooter()

	if not parse.readFileText("credits-footer.tbl", "data/tables") then
		return
	end

	local text = "<br></br><br></br>"

	while(not parse.optionalString("#end")) do
		local this_string = parse.getString()

		text = text .. this_string .. "<br></br>"
	end

	self.CreditsFooter = text

	parse.stop()

end

--- Get the image file to display and reset the timers and alpha settings
--- @return nil
function CreditsController:chooseImage()
	local image_idx = self.CreditsImage.Index

	if self.CreditsImage.Timer <= 0 then
		if not self.CreditsImage.ImageFile2 then
			self.CreditsImage.Index = self.CreditsImage.Index + 1
			self.CreditsImage.ImageFile2 = self.CreditsImage.ImageFile1
			self.CreditsImage.Alpha = 1.0
		end
		if self.CreditsImage.FadeTimer > 0 then
			self.CreditsImage.FadeTimer = self.CreditsImage.FadeTimer - 0.01
			self.CreditsImage.Alpha = self.CreditsImage.Alpha - self.CreditsImage.FadeAmount
		else
			self.CreditsImage.FadeTimer = ui.TechRoom.Credits.FadeTime
			self.CreditsImage.Timer = ui.TechRoom.Credits.DisplayTime
			self.CreditsImage.ImageFile2 = nil
			self.CreditsImage.Alpha = 0
		end
	end

	if self.CreditsImage.Index >= ui.TechRoom.Credits.NumImages then
		self.CreditsImage.Index = 0
	end

	local image_idx_string = tostring(image_idx)
	if self.CreditsImage.Index < 10 then
		image_idx_string = "0" .. self.CreditsImage.Index
	end

	self.CreditsImage.ImageFile1 = "2_Crim" .. image_idx_string .. ".png"
end

--- Runs the image timers and calls the choose image function
--- @return nil
function CreditsController:timeImages()
	async.run(function()
        async.await(AsyncUtil.wait_for(0.01))
        self.CreditsImage.Timer = self.CreditsImage.Timer - 0.01
		self:chooseImage()
		self:timeImages()
    end, async.OnFrameExecutor)
end

--- Draw the credits image
--- @return nil
function CreditsController:drawImage()
	--Disabled for now. Needs to draw to a texture and that texture printed to the background instead of drawing on top of everything

	--[[if self.creditsImage.ImageFile2 then
		gr.drawImage(self.creditsImage.ImageFile2, self.creditsImage.X1, self.creditsImage.Y1, self.creditsImage.X2, self.creditsImage.Y2, 0, 0 , 1, 1, self.creditsImage.Alpha)
	end
	if self.creditsImage.ImageFile1 then
		gr.drawImage(self.creditsImage.ImageFile1, self.creditsImage.X1, self.creditsImage.Y1, self.creditsImage.X2, self.creditsImage.Y2, 0, 0 , 1, 1, (1.0 - self.creditsImage.Alpha))
	end]]--
end

--- Initialize the music system and calculate the total time to play all music
--- @return nil
function CreditsController:initMusic()
	self.Track_Lengths = {}
	self.TotalMusicTime = 0
	for i = 1, #self.MusicInfo.Music_Files do
		local handle = ad.openAudioStream(self.MusicInfo.Music_Files[i], AUDIOSTREAM_MENUMUSIC)
		self.Track_Lengths[i] = handle:getDuration() * 1000
		self.TotalMusicTime = self.TotalMusicTime + self.Track_Lengths[i]
		handle:close(false)
	end
	ba.print("Total credits scroll time is " .. self.TotalMusicTime .. "\n")
	self.MusicInfo.CurrentTrack = 0
end

--- Check if the current track is done, and if so, play the next track
--- @return nil
function CreditsController:maybePlayTrack()

	--music isn't initialized yet, so bail
	if self.MusicInfo.CurrentTrack == nil then
		return
	end

	if self.MusicInfo.MusicHandle ~= nil then
		if self.MusicInfo.MusicHandle:isPlaying() then
			return
		else
			if self.MusicInfo.Waiting then
				return
			else
				self.MusicInfo.MusicHandle:close(false)
				self.MusicInfo.MusicHandle = nil
			end
		end
	else
		if self.MusicInfo.CurrentTrack < #self.MusicInfo.Music_Files then
			self.MusicInfo.CurrentTrack = self.MusicInfo.CurrentTrack + 1
			self:startMusic(self.MusicInfo.CurrentTrack)
		end
	end
end

--- Start playing the specified music track
--- @param track number the track number to play
--- @return nil
function CreditsController:startMusic(track)
	self.MusicInfo.MusicHandle = ad.openAudioStream(self.MusicInfo.Music_Files[track], AUDIOSTREAM_MENUMUSIC)
	self.MusicInfo.Waiting = true
	async.run(function()
        async.await(AsyncUtil.wait_for(0.5))
		self.MusicInfo.Waiting = false
        self.MusicInfo.MusicHandle:play(ad.MasterEventMusicVolume, false)
    end, async.OnFrameExecutor)
end

--- Begin the fade in effect
--- @return nil
function CreditsController:beginFadeIn()

	if self.FadeInData.Active == true then
		return
	end

	self.FadeInData.Current = 255
	self.FadeInData.Active = true
	self.FadeInData.Timing = 2

	self:calcFadeIn()
end

--- Calculate how much to fade in each frame
--- @return nil
function CreditsController:calcFadeIn()

	if self.FadeInData.Current <= 0 then
		self.FadeInData.Active = false
		return
	end

	local diff_per_second = math.floor(255 / self.FadeInData.Timing)
	local diff_per_ms = math.ceil(diff_per_second / 1000)

	self.FadeInData.Current = self.FadeInData.Current - diff_per_ms

	async.run(function()
        async.await(AsyncUtil.wait_for(0.001))
        self:calcFadeIn()
    end, async.OnFrameExecutor)
end

--- Draw the fade in effect
function CreditsController:fadeIn()

	if self.FadeInData.Active == false then
		return
	end

	local r, g, b, a = gr.getColor()
	local width = gr.getScreenWidth()
	local height = gr.getScreenHeight()

	gr.setColor(0,0,0, self.FadeInData.Current)
	gr.drawRectangle(0,0,width, height)

	--reset the color back to what it was
	gr.setColor(r, g, b, a)

end

--- Show the help text if the player presses escape. Hide it after 5 seconds
--- @return nil
function CreditsController:showHelp()
	local help_el = self.Document:GetElementById("escape_help")

	CreditsController.help_visible = true
	help_el:SetClass("hidden", false)

	async.run(function()
        async.await(AsyncUtil.wait_for(5))
		if CreditsController.help_visible then
			CreditsController:hideHelp(help_el)
		end
    end, async.OnFrameExecutor)
end

--- Hide the help text
--- @param help_el Element the help element
--- @return nil
function CreditsController:hideHelp(help_el)
	CreditsController.help_visible = false
	help_el:SetClass("hidden", true)
end

--- Begin the fade out effect
--- @return nil
function CreditsController:beginFadeOut()

	if self.FadeOutData.Active == true then
		return
	end

	self.FadeOutData.Current = 0
	self.FadeOutData.Active = true
	self.FadeOutData.Timing = 3

	self:calcFadeOut()

	async.run(function()
        async.await(AsyncUtil.wait_for(2.5))
		if self.MusicInfo.MusicHandle ~= nil then
			self.MusicInfo.MusicHandle:close(true)
		end
    end, async.OnFrameExecutor)
end

--- Calculate how much to fade out each frame
--- @return nil
function CreditsController:calcFadeOut()

	if self.FadeOutData.Current >= 255 then
		return
	end

	local diff_per_second = math.floor(255 / self.FadeOutData.Timing)
	local diff_per_ms = math.ceil(diff_per_second / 1000)

	self.FadeOutData.Current = self.FadeOutData.Current + diff_per_ms

	async.run(function()
        async.await(AsyncUtil.wait_for(0.001))
        self:calcFadeOut()
    end, async.OnFrameExecutor)
end

--- Draw the fade out effect
--- @return nil
function CreditsController:fadeOut()

	if self.FadeOutData.Active == false then
		return
	end

	local r, g, b, a = gr.getColor()
	local width = gr.getScreenWidth()
	local height = gr.getScreenHeight()

	gr.setColor(0,0,0, self.FadeOutData.Current)
	gr.drawRectangle(0,0,width, height)

	--reset the color back to what it was
	gr.setColor(r, g, b, a)

	if self.FadeOutData.Current >= 255 then
		if ScpuiSystem.ForceCredits then
			ba.postGameEvent(ba.GameEvents["GS_EVENT_MAIN_MENU"])
		else
			ba.postGameEvent(ba.GameEvents["GS_EVENT_INITIAL_PLAYER_SELECT"])
			ScpuiSystem.MainMenuReturn = true -- Used to determine how the main menu looks when started
		end
		self.FadeOutData.Active = false
		self.FadeOutData.Current = 0
		return
	end
end

--- Scroll the credits by the increment every 0.01 seconds
--- @return nil
function CreditsController:scrollCredits()
	if self.ScrollPosition >= self.ScrollTotalHeight then
		if self.FadeOutData.Active == false then
			async.run(function()
				async.await(AsyncUtil.wait_for(3))
				self:beginFadeOut()
			end, async.OnFrameExecutor)
		end
	else
		self.ScrollPosition = self.ScrollPosition + self.ScrollIncrement
	end
	self.CreditsElement.scroll_top = self.ScrollPosition

	async.run(function()
        async.await(AsyncUtil.wait_for(0.01))
        self:scrollCredits()
    end, async.OnFrameExecutor)
end

--- Global keydown function handles all keypresses
--- @param element Element The main document element
--- @param event Event The event that was triggered
--- @return nil
function CreditsController:global_keydown(element, event)
    if event.parameters.key_identifier == rocket.key_identifier.ESCAPE then
		if CreditsController.help_visible then
			self:hideHelp(self.Document:GetElementById("escape_help"))
			self:beginFadeOut()
		else
			self:showHelp()
		end
	end
end

--- Called when the screen is being unloaded
--- @return nil
function CreditsController:unload()
	if ScpuiSystem.ForceCredits then
		--ui.Debriefing.acceptMission()
		--mn.unloadMission(true)
		ScpuiSystem.ForceCredits = nil
	end

	if self.MusicInfo.MusicHandle ~= nil and self.MusicInfo.MusicHandle:isValid() then
		self.MusicInfo.MusicHandle:close(false)
	end

	ScpuiSystem.CreditsContext = nil

	--reveal the mouse
	io.setCursorHidden(false)
end

--- Run all the frame functions
--- @return nil
function CreditsController:runFrameFunctions()
	--self:drawImage()
	self:maybePlayTrack()
	self:fadeIn()
	self:fadeOut()
end

engine.addHook("On Frame", function()
	if ScpuiSystem.CreditsContext then
		ScpuiSystem.CreditsContext:runFrameFunctions()
	end
end, {State="GS_STATE_SCRIPTING"}, function()
    return false
end)

return CreditsController
