local dialogs = require("dialogs")
local class = require("class")
local md = require("debugFunctions")
local utils = require("utils")
local async_util = require("async_util")
local topics = require("ui_topics")
local datasaver = require("datasaver")

local TechNodeMapController = class()

ScpuiSystem.modelDraw = nil

function TechNodeMapController:init()
	self.help_shown = false
	self.nodes = {}
	self.scale = 1
end

function TechNodeMapController:initialize(document)
    self.document = document
    self.elements = {}
	
	self.Selected = "No Selection"
	self.selectedText = "Please make a selection."
	
	---Load background choice
	self.document:GetElementById("main_background"):SetClass(ScpuiSystem:getBackgroundClass(), true)
	
	--For BtA: Set the class custom. This is a thing because Journal needs to be modular to SCPUI as well as BtA.
	--So eventually this will need a separate file to add it's own hooks into ui_topics that can then be referenced.
	--But for now we just brute force it.
	self.document:GetElementById("main_background"):SetClass("node_map_bg", true)

	---Load the desired font size from the save file
	self.document:GetElementById("main_background"):SetClass(("base_font" .. ScpuiSystem:getFontPixelSize()), true)
	
	self.document:GetElementById("tech_btn_1"):SetPseudoClass("checked", false)
	self.document:GetElementById("tech_btn_2"):SetPseudoClass("checked", true)
	self.document:GetElementById("tech_btn_3"):SetPseudoClass("checked", false)
	self.document:GetElementById("tech_btn_4"):SetPseudoClass("checked", false)
	
	self.saveData = datasaver:loadDataFromFile("nodemap", true) or {}
	
	--Get the key for the game progression
	self.Key = self:getPlayerProgress()
	
	topics.techroom.initialize:send(self)
	
	self.mainMap = self.document:GetElementById("map_view")
	self.Width = self.mainMap.client_width
	self.Height = self.mainMap.client_height
	
	self.texture = gr.createTexture(self.Width, self.Height)
	self.url = ui.linkTexture(self.texture)
	
	self.Background = "mapgrid.png"

  if ba.inDebug() then
    NodeMapData:parseTables()
  end
	
	--Setup all the button data
	for k, v in pairs(NodeMapData.entries) do
		self:SetupButton(v)
	end
	
	--Get the key for the game progression
	self.Key = self:getPlayerProgress()
	
	local mapEl = self.document:CreateElement("img")
	mapEl:SetAttribute("src", self.url)
	self.mainMap:AppendChild(mapEl)
	
	--Get the image size
	local sys_w = gr.getImageWidth(self.Background)
	local sys_h = gr.getImageHeight(self.Background)
	
	--Get the screen size
	local screen_w = gr.getScreenWidth()
	local screen_h = gr.getScreenHeight()

	--Get the scale factor
	if screen_w > screen_h then
		self.scale = self:calculateScaleFactor(screen_w, sys_w)
	else
		self.scale = self:calculateScaleFactor(screen_h, sys_h)
	end
	
	self:GenerateIconButtons(NodeMapData.entries)
	
	--Set the background
	self.lines = true
	self.document:GetElementById("oval_btn"):SetPseudoClass("checked", not self.lines)
	self:UpdateBackground()
	
end

function TechNodeMapController:getPlayerProgress()
	local BtA = require("btafunctions")
	return BtA:getGameProgress()
end

function TechNodeMapController:ChangeTechState(state)

	if state == 1 then
		topics.techroom.btn1Action:send()
	end
	if state == 2 then
        --This is where we are already, so don't do anything
		--topics.techroom.btn2Action:send()
	end
	if state == 3 then
		topics.techroom.btn3Action:send()
	end
	if state == 4 then
		topics.techroom.btn4Action:send()
	end
	
end

function TechNodeMapController:getProgressionOverride(entry)
	for i, v in pairs(entry.Progression) do
		if self.Key >= v.Key then
			return v
		end
	end
	
	return nil
end

function TechNodeMapController:DrawNodes()
	for _, v in pairs(NodeMapData.entries) do
		if #v.Nodes > 0 then
			for _, node in pairs(v.Nodes) do
				local diameter = 4 * self.scale
				
				local centerX = (v.button_el.client_width / 2) + v.BitmapX
				local centerY = (v.button_el.client_height / 2) + v.BitmapY
				
				local x, y = self:PolarToXY(20 * self.scale, node.Angle, centerX, centerY)
				--No idea why the x coord needs adjustment to be properly centered and Y does not!
				x = x - (diameter/4)
				
				--Get the current values
				local newColor = gr.createColor(node.Color[1], node.Color[2], node.Color[3], 255)
				local noLine = node.NoLine
				local enforce = node.EnforceColor
				
				--Check for not override data
				local override = self:getProgressionOverride(v)
				if override ~= nil then
					for _, n in pairs(override.Nodes) do
						if n.Name == node.Name then
							if n.Color then
								newColor = gr.createColor(n.Color[1], n.Color[2], n.Color[3], 255)
							end
							if n.NoLine then
								noLine = n.NoLine
							end
							if n.EnforceColor then
								enforce = n.EnforceColor
							end
						end
					end
				end
				
				gr.setColor(newColor)
				gr.drawCircle(diameter, x, y, true)
				
				if not noLine then
					local pair = self:getNodePair(v.Name, node.Name)
					
					if pair == nil then
						local entry = {
							Points = {v.Name, node.Name},
							First = {x, y, newColor, enforce},
							Second = nil
						}
						table.insert(self.nodes, entry)
					else
						if pair.Second == nil then
							pair.Second = {x, y, newColor, enforce}
						else
							ba.warning("Nodemap.tbl suggests a duplicate node pair for " .. v.Name .. ": " .. node.Name)
						end
					end
				end
			end
		end
	end
end

function TechNodeMapController:getNodePair(first, second)
	for _, v in pairs(self.nodes) do
		if string.lower(v.Points[1]) == string.lower(first) then
			if string.lower(v.Points[2]) == string.lower(second) then
				return v
			end
		elseif string.lower(v.Points[2]) == string.lower(first) then
			if string.lower(v.Points[1]) == string.lower(second) then
				return v
			end
		end
	end
	
	return nil
end

function TechNodeMapController:GetLineColor(first, second)
	if first[4] == true then
		return first[3]
	else
		if second[4] == true then
			return second[3]
		else
			return first[3]
		end
	end
end

function TechNodeMapController:DrawNodeLines()
	for i, v in ipairs(self.nodes) do
		if v.First == nil or v.Second == nil then
			--ba.warning("NodeMap UI got command to draw a node line for nodes '" .. v.Points[1] .. ":" .. v.Points[2] .. "' that has an incomplete pair! Skipping!")
		else
			local color = self:GetLineColor(v.First, v.Second)
			color.Alpha = 128
			gr.setColor(color)
			gr.drawLine(v.First[1], v.First[2], v.Second[1], v.Second[2])
		end
	end
end

function TechNodeMapController:toggle_node_lines()
	self.lines = not self.lines
	self.nodes = {}
	self:UpdateBackground()
	self.document:GetElementById("oval_btn"):SetPseudoClass("checked", not self.lines)
end

function TechNodeMapController:UpdateBackground()

	--save the color
	local color = gr.getColor(true)
	gr.setLineWidth(1.0)

	gr.setTarget(self.texture)
	
	gr.clearScreen(0,0,0,255)
	gr.drawImage(self.Background, 0, 0, self.Width, self.Height)
	
	self:DrawNodes()
	
	if self.lines then
		self:DrawNodeLines()
	end
	
	gr.setTarget()
	
	--reset the color
	gr.setColor(color)
	
end

function TechNodeMapController:CleanupBackground()

	while self.mainMap:HasChildNodes() do
		self.mainMap:RemoveChild(self.mainMap.first_child)
	end
	
	self.texture:unload()
	self.texture = nil
	
end

--Sets coordinates for a button according to its specified polar coordinates
function TechNodeMapController:SetupButton(button)
	
	--Convert floats to coords
	button.convertedX = button.X * self.Width
	button.convertedY = button.Y * self.Height
	
	--Set up the bitmap and text positions
	button.BitmapX = button.convertedX - ((gr.getImageWidth(button.Bitmap)/2) * self.scale)-- + (button.Offset.X * self.scale)
	button.BitmapY = button.convertedY - ((gr.getImageHeight(button.Bitmap)/2) * self.scale)--  + (button.Offset.Y * self.scale)

end

function TechNodeMapController:formatNameToId(name, suffix)
	local lowerName = name:lower()
	local formattedName = lowerName:gsub("%s", "_")
	return formattedName .. suffix
end

--Look through the Icons Table and generate buttons
function TechNodeMapController:GenerateIconButtons(iconTable)
	
	local parent_el = self.mainMap
			
	for key, value in pairs(iconTable) do

		if value.Visible then
		
			--Set the values
			local bitmap = value.Bitmap
			local override = self:getProgressionOverride(value)
			if override ~= nil then
				if override.Bitmap then
					bitmap = override.Bitmap
				end
			end
	
			local button_el = nil
			if value.Selectable then
				button_el = self.document:CreateElement("button")
				button_el:SetClass("button_1", true)
			else
				button_el = self.document:CreateElement("div")
			end
			button_el:SetClass("icon", true)
			button_el:SetClass("icon_button", true)
			
			local span_el = self.document:CreateElement("span")
			span_el:SetClass("icon")
			
			local img_el = self.document:CreateElement("img")
			img_el:SetClass("pseudo_img")
			if not bitmap then
				ba.warning("Icon '" .. value.Name .. "' has no bitmap defined!")
			end
			img_el:SetAttribute("src", bitmap)
			img_el.style.width = math.floor(gr.getImageWidth(bitmap) * self.scale) .. "px"
			
			span_el:AppendChild(img_el)
			button_el:AppendChild(span_el)
			parent_el:AppendChild(button_el)
			
			if not value.HideLabel then
				local text_el = self.document:CreateElement("span")
				text_el:SetClass("button_text_bottom", true)
				text_el:SetClass("pos", true)
				text_el:SetClass("icon_text", true)
				text_el.inner_rml = "<p>" .. value.DisplayName .. "</p>"
				button_el:AppendChild(text_el)
			end
			
			if value.Selectable then
			
				button_el:AddEventListener("mouseover", function(_, _, _)
					img_el:SetAttribute("src", bitmap .. "_h")
				end)
				
				button_el:AddEventListener("mouseout", function(_, _, _)
					if self.Selected ~= value.Name then
						img_el:SetAttribute("src", bitmap)
					end
				end)
				
				button_el:AddEventListener("mousedown", function(_, _, _)
					img_el:SetAttribute("src", bitmap .. "_c")
				end)
				
				button_el:AddEventListener("mouseup", function(_, _, _)
					img_el:SetAttribute("src", bitmap .. "_h")
				end)
				
				button_el:AddEventListener("click", function(_, _, _)
					self:SelectEntry(value, button_el)
				end)
				
				button_el:AddEventListener("dblclick", function(_, _, _)
					self:SelectEntry(value, button_el)
					self:BreakoutReader()
				end)
				
				local lastRead = self.saveData[value.Name]
				
				local new_el = self.document:CreateElement("div")
				new_el.id = self:formatNameToId(value.Name, "_new")
				new_el:SetClass("new", true)
				new_el.inner_rml = "!"
				button_el:AppendChild(new_el)
				if not lastRead or  (lastRead < self.Key and override) then
					new_el:SetClass("hidden", false)
				else
					new_el:SetClass("hidden", true)
				end
				
			end
			
			--Setup the button position
			button_el.style.top = value.BitmapY .. "px"
			button_el.style.left = value.BitmapX .. "px"
			button_el.style.width = math.floor(gr.getImageWidth(bitmap) * self.scale) .. "px"
			value.button_el = button_el
			value.text_el = text_el
			value.img_el = img_el
		end
	end

end

function TechNodeMapController:DeselectAll()
	for k, v in pairs(NodeMapData.entries) do
		--Set the values
		local bitmap = v.Bitmap
		--Check for an override
		local override = self:getProgressionOverride(v)
		if override ~= nil then
			if override.Bitmap then
				bitmap = override.Bitmap
			end
		end
		v.img_el:SetAttribute("src", bitmap)
	end
	
	if self.SelectedNodeBtn ~= nil then
		self.SelectedNodeBtn:SetPseudoClass("checked", false)
	end
end

function TechNodeMapController:SelectEntry(entry, button)
	self.Selected = entry.Name
	self:DeselectAll()
	button:SetPseudoClass("checked", true)
	self.SelectedNodeBtn = button
	
	self.saveData[entry.Name] = self.Key
	datasaver:saveDataToFile("nodemap", self.saveData, true)
	
	local new_el = self.document:GetElementById(self:formatNameToId(entry.Name, "_new"))
	new_el:SetClass("hidden", true)
	
	--Set the values
	local bitmap = entry.Bitmap
	local faction = entry.Faction
	local description = entry.Description
	
	--Check for overrides
	local override = self:getProgressionOverride(entry)
	if override ~= nil then
		if override.Bitmap then
			bitmap = override.Bitmap
		end
		if override.Faction then
			faction = override.Faction
		end
		if override.Description then
			description = override.Description
		end
	end
	
	if faction == "" then
		faction = utils.xstr("Ungoverned", -1)
	end
	
	entry.img_el:SetAttribute("src", bitmap .. "_h")
	local text_name = "<span class=\"white\">System: </span>" .. entry.DisplayName .. "<br/>"
	local text_type = "<span class=\"white\">Type of System: </span>" .. entry.SysType .. "<br/>"
	local text_faction = "<span class=\"white\">Controlling Faction: </span>" .. faction .. "<br/><br/>"
	local text = "<p>" .. text_name .. text_type .. text_faction .. description .. "</p>"
	self.document:GetElementById("map_desc").inner_rml = text
	self.document:GetElementById("map_desc").scroll_top = 0
	
	--For the Read in Window option
	self.selectedText = text_type .. text_faction .. description
	
	--Hide the make a selection text
	self.document:GetElementById("make_a_selection"):SetClass("hidden", true)
end

function TechNodeMapController:Show(text, title, buttons)
	--Create a simple dialog box with the text and title

	currentDialog = true
	
	local dialog = dialogs.new()
		dialog:title(title)
		dialog:text(text)
		dialog:escape("")
		dialog:clickescape(true)
		for i = 1, #buttons do
			dialog:button(buttons[i].b_type, buttons[i].b_text, buttons[i].b_value, buttons[i].b_keypress)
		end
		dialog:background("#00000080")
		dialog:show(self.document.context)
		:continueWith(function(response)
    end)
	-- Route input to our context until the user dismisses the dialog box.
	ui.enableInput(self.document.context)
end

function TechNodeMapController:BreakoutReader()
	local text = self.selectedText
	local title = "<span style=\"color:white;\">" .. self.Selected .. "</span>"
	local buttons = {}
	buttons[1] = {
		b_type = dialogs.BUTTON_TYPE_POSITIVE,
		b_text = ba.XSTR("Close", 11625),
		b_value = "",
		b_keypress = string.sub(ba.XSTR("Close", 11625), 1, 1)
	}
	self:Show(text, title, buttons)
end

function TechNodeMapController:ClearData()
	self.document:GetElementById("tech_desc").inner_rml = "<p></p>"
end

--Convert polar coordinates to rectangular coordinates. By default uses the middle of the background as an origin point.
function TechNodeMapController:PolarToXY(r, theta, origin_x, origin_y)

	theta = math.rad(theta)
	
	local x, y = (r * math.cos(theta)) + origin_x, (r * math.sin(theta)) + origin_y
		
	return x, y

end

--Function to calculate the scale factor
function TechNodeMapController:calculateScaleFactor(screenWidth, imageWidth)
    --Ensure both inputs are positive numbers
    if screenWidth <= 0 or imageWidth <= 0 then
		ba.warning("Screen width or image with was <= 0! Using scale factor of 1!")
        return 1
    end

    local scaleFactor = screenWidth / imageWidth

	--Add 7 percent because they are designed as 2048w images on a 1920w screen
	--which is basically 7% bigger than the screen. This ensures the way these are designed
	--is recreated on all screen sizes very closely.
    return scaleFactor * 1.07
end

function TechNodeMapController:global_keydown(element, event)
    if event.parameters.key_identifier == rocket.key_identifier.ESCAPE then
        event:StopPropagation()

        ba.postGameEvent(ba.GameEvents["GS_EVENT_MAIN_MENU"])
    elseif event.parameters.key_identifier == rocket.key_identifier.S and event.parameters.ctrl_key == 1 and event.parameters.shift_key == 1 then
		self.show_all  = not self.show_all
		self:ReloadList()
	elseif event.parameters.key_identifier == rocket.key_identifier.UP and event.parameters.ctrl_key == 1 then
		self:ChangeTechState(4)
	elseif event.parameters.key_identifier == rocket.key_identifier.DOWN and event.parameters.ctrl_key == 1 then
		self:ChangeTechState(2)
	elseif event.parameters.key_identifier == rocket.key_identifier.TAB then
		local newSection = self.sectionIndex + 1
		if newSection == 4 then
			newSection = 1
		end
		self:ChangeSection(newSection)
	elseif event.parameters.key_identifier == rocket.key_identifier.UP then
		self:ScrollText(self.document:GetElementById("map_desc"), 0)
	elseif event.parameters.key_identifier == rocket.key_identifier.DOWN then
		self:ScrollText(self.document:GetElementById("map_desc"), 1)
	elseif event.parameters.key_identifier == rocket.key_identifier.RETURN then
		--self:commit_pressed(element)
	elseif event.parameters.key_identifier == rocket.key_identifier.F1 then
		self:help_clicked(element)
	elseif event.parameters.key_identifier == rocket.key_identifier.F2 then
		self:options_button_clicked(element)
	end
end

function TechNodeMapController:ScrollText(element, direction)
	if direction == 0 then
		element.scroll_top = (element.scroll_top - 5)
	else
		element.scroll_top = (element.scroll_top + 5)
	end
end

function TechNodeMapController:commit_pressed(element)
    ui.playElementSound(element, "click", "success")
    ba.postGameEvent(ba.GameEvents["GS_EVENT_MAIN_MENU"])
end

function TechNodeMapController:options_button_clicked(element)
    ui.playElementSound(element, "click", "success")
    ba.postGameEvent(ba.GameEvents["GS_EVENT_OPTIONS_MENU"])
end

function TechNodeMapController:help_clicked(element)
    ui.playElementSound(element, "click", "success")
    
	self.help_shown  = not self.help_shown

    local help_texts = self.document:GetElementsByClassName("tooltip")
    for _, v in ipairs(help_texts) do
        v:SetPseudoClass("shown", self.help_shown)
    end
end

function TechNodeMapController:unload()
    if self.texture ~= nil then
		self.texture:destroyRenderTarget()
		self.texture:unload()
	end
end

return TechNodeMapController
