-- Path of Building
--
-- Module: Main
-- Main module of program.
--
local launch = ...

local ipairs = ipairs
local t_insert = table.insert
local t_remove = table.remove
local m_floor = math.floor
local m_max = math.max
local m_min = math.min

LoadModule("Modules/Common")
LoadModule("Modules/Data")
LoadModule("Modules/ModTools")
LoadModule("Modules/ItemTools")

LoadModule("Classes/ControlHost")

local main = common.New("ControlHost")

local classList = {
	"UndoHandler",
	-- Basic controls
	"Control",
	"LabelControl",
	"SectionControl",
	"ButtonControl",
	"CheckBoxControl",
	"EditControl",
	"DropDownControl",
	"ScrollBarControl",
	"SliderControl",
	"TextListControl",
	-- Misc
	"PopupDialog",
	-- Mode: Build list
	"BuildListControl",
	-- Mode: Build
	"ModList",
	"ModDB",
	"ImportTab",
	"NotesTab",
	"ConfigTab",
	"TreeTab",
	"PassiveTree",
	"PassiveSpec",
	"PassiveTreeView",
	"SkillsTab",
	"SkillListControl",
	"SlotSelectControl",
	"GemSelectControl",
	"ItemsTab",
	"ItemSlotControl",
	"ItemListControl",
	"ItemDBControl",
	"CalcsTab",
	"CalcSectionControl",
	"CalcBreakdownControl",
}
for _, className in pairs(classList) do
	LoadModule("Classes/"..className, launch, main)
end

function main:Init()
	self.modes = { }
	self.modes["LIST"] = LoadModule("Modules/BuildList", launch, self)
	self.modes["BUILD"] = LoadModule("Modules/Build", launch, self)

	if launch.devMode or GetScriptPath() == GetRuntimePath() then
		-- If running in dev mode or standalone mode, put user data in the script path
		self.userPath = GetScriptPath().."/"
	else
		self.userPath = GetUserPath().."/Path of Building/"
		MakeDir(self.userPath)
	end
	self.buildPath = self.userPath.."Builds/"
	MakeDir(self.buildPath)
	
	self.tree = common.New("PassiveTree")

	ConPrintf("Loading item databases...")
	self.uniqueDB = { list = { } }
	for type, typeList in pairs(data.uniques) do
		for _, raw in pairs(typeList) do
			local newItem = itemLib.makeItemFromRaw("Rarity: Unique\n"..raw)
			if newItem then
				self.uniqueDB.list[newItem.name] = newItem
			else
				ConPrintf("Unique DB unrecognised item of type '%s':\n%s", type, raw)
			end
		end
	end
	self.rareDB = { list = { } }
	for _, raw in pairs(data.rares) do
		local newItem = itemLib.makeItemFromRaw(raw)
		if newItem then
			self.rareDB.list[newItem.name] = newItem
		else
			ConPrintf("Rare DB unrecognised item:\n%s", raw)
		end
	end

	self.anchorUpdate = common.New("Control", nil, 2, 0, 0, 0)
	self.anchorUpdate.y = function()
		return self.screenH - 4
	end
	self.controls.applyUpdate = common.New("ButtonControl", {"BOTTOMLEFT",self.anchorUpdate,"BOTTOMLEFT"}, 0, 0, 110, 20, "^x50E050Update Ready", function()
		local changeList = { }
		for line in io.lines("changelog.txt") do
			local ver, date = line:match("^VERSION%[(.+)%]%[(.+)%]$")
			if ver then
				if ver == launch.versionNumber then
					break
				end
				if #changeList > 0 then
					t_insert(changeList, { height = 12 })
				end
				t_insert(changeList, { height = 20, "^7Version "..ver.." ("..date..")" })
			else
				t_insert(changeList, { height = 14, "^7"..line })
			end
		end
		self:OpenPopup(800, 250, "Update Available", {
			common.New("TextListControl", nil, 0, 20, 780, 190, nil, changeList),
			common.New("ButtonControl", nil, -45, 220, 80, 20, "Update", function()
				launch:ApplyUpdate(launch.updateAvailable)
				main:ClosePopup()
			end),
			common.New("ButtonControl", nil, 45, 220, 80, 20, "Cancel", function()
				main:ClosePopup()
			end),
		})
	end)
	self.controls.applyUpdate.shown = function()
		return launch.updateAvailable and launch.updateAvailable ~= "none"
	end
	self.controls.checkUpdate = common.New("ButtonControl", {"BOTTOMLEFT",self.anchorUpdate,"BOTTOMLEFT"}, 0, 0, 120, 18, "", function()
		launch:CheckForUpdate()
	end)
	self.controls.checkUpdate.shown = function()
		return not launch.devMode and (not launch.updateAvailable or launch.updateAvailable == "none")
	end
	self.controls.checkUpdate.label = function()
		return launch.subScriptType == "UPDATE" and launch.updateProgress or "Check for Update"
	end
	self.controls.checkUpdate.enabled = function()
		return not IsSubScriptRunning()
	end
	self.controls.versionLabel = common.New("LabelControl", {"BOTTOMLEFT",self.anchorUpdate,"BOTTOMLEFT"}, 124, 0, 0, 14, "")
	self.controls.versionLabel.label = function()
		return "^8Version: "..launch.versionNumber..(launch.versionBranch == "dev" and " (Dev)" or "")
	end
	self.controls.about = common.New("ButtonControl", {"BOTTOMLEFT",self.anchorUpdate,"BOTTOMLEFT"}, 250, 0, 50, 20, "About", function()
		local changeList = { }
		for line in io.lines("changelog.txt") do
			local ver, date = line:match("^VERSION%[(.+)%]%[(.+)%]$")
			if ver then
				if #changeList > 0 then
					t_insert(changeList, { height = 10 })
				end
				t_insert(changeList, { height = 18, "^7Version "..ver.." ("..date..")" })
			else
				t_insert(changeList, { height = 12, "^7"..line })
			end
		end
		self:OpenPopup(650, 400, "About", {
			common.New("ButtonControl", {"TOPRIGHT",nil,"TOPRIGHT"}, -10, 10, 50, 20, "Close", function()
				main:ClosePopup()
			end),
			common.New("LabelControl", nil, 0, 18, 0, 18, "Path of Building v"..launch.versionNumber.." by Openarl"),
			common.New("ButtonControl", nil, 0, 42, 420, 18, "Forum Thread: ^x4040FFhttps://www.pathofexile.com/forum/view-thread/1716360", function(control)
				if OpenURL then
					OpenURL("https://www.pathofexile.com/forum/view-thread/1716360")
				end
			end),
			common.New("ButtonControl", nil, 0, 64, 340, 18, "GitHub page: ^x4040FFhttps://github.com/Openarl/PathOfBuilding", function(control)
				if OpenURL then
					OpenURL("https://github.com/Openarl/PathOfBuilding")
				end
			end),
			common.New("LabelControl", {"TOPLEFT",nil,"TOPLEFT"}, 10, 82, 0, 18, "^7Version history:"),
			common.New("TextListControl", nil, 0, 100, 630, 290, nil, changeList),
		})
	end)
	self.controls.devMode = common.New("LabelControl", {"BOTTOMLEFT",self.anchorUpdate,"BOTTOMLEFT"}, 0, 0, 0, 18, "^1Dev Mode")
	self.controls.devMode.shown = function()
		return launch.devMode
	end

	self.inputEvents = { }
	self.popups = { }
	self.tooltipLines = { }

	self.accountSessionIDs = { }

	self.buildSortMode = "NAME"

	self:SetMode("BUILD", false, "Unnamed build")

	self:LoadSettings()
end

function main:CanExit()
	local ret = self:CallMode("CanExit")
	if ret ~= nil then
		return ret
	else
		return true
	end
end

function main:Shutdown()
	self:CallMode("Shutdown")

	self:SaveSettings()
end

function main:OnFrame()
	self.screenW, self.screenH = GetScreenSize()

	while self.newMode do
		if self.mode then
			self:CallMode("Shutdown")
		end
		self.mode = self.newMode
		self.modeArgs = self.newModeArgs
		self.newMode = nil
		self:CallMode("Init", unpack(self.modeArgs))
	end

	self.viewPort = { x = 0, y = 0, width = self.screenW, height = self.screenH }

	if self.popups[1] then
		self.popups[1]:ProcessInput(self.inputEvents, self.viewPort)
		wipeTable(self.inputEvents)
	else
		self:ProcessControlsInput(self.inputEvents, self.viewPort)
	end

	self:CallMode("OnFrame", self.inputEvents, self.viewPort)

	self:DrawControls(self.viewPort)

	if self.popups[1] then
		SetDrawLayer(10)
		SetDrawColor(0, 0, 0, 0.5)
		DrawImage(nil, 0, 0, self.screenW, self.screenH)
		self.popups[1]:Draw(self.viewPort)
		SetDrawLayer(0)
	end

	wipeTable(self.inputEvents)
end

function main:OnKeyDown(key, doubleClick)
	t_insert(self.inputEvents, { type = "KeyDown", key = key, doubleClick = doubleClick })
end

function main:OnKeyUp(key)
	t_insert(self.inputEvents, { type = "KeyUp", key = key })
end

function main:OnChar(key)
	t_insert(self.inputEvents, { type = "Char", key = key })
end

function main:SetMode(newMode, ...)
	self.newMode = newMode
	self.newModeArgs = {...}
end

function main:CallMode(func, ...)
	local modeTbl = self.modes[self.mode]
	if modeTbl[func] then
		return modeTbl[func](modeTbl, ...)
	end
end

function main:LoadSettings()
	local setXML, errMsg = common.xml.LoadXMLFile(self.userPath.."Settings.xml")
	if not setXML then
		return true
	elseif setXML[1].elem ~= "PathOfBuilding" then
		launch:ShowErrMsg("^1Error parsing 'Settings.xml': 'PathOfBuilding' root element missing")
		return true
	end
	for _, node in ipairs(setXML[1]) do
		if type(node) == "table" then
			if node.elem == "Mode" then
				if not node.attrib.mode or not self.modes[node.attrib.mode] then
					launch:ShowErrMsg("^1Error parsing 'Settings.xml': Invalid mode attribute in 'Mode' element")
					return true
				end
				local args = { }
				for _, child in ipairs(node) do
					if type(child) == "table" then
						if child.elem == "Arg" then
							if child.attrib.number then
								t_insert(args, tonumber(child.attrib.number))
							elseif child.attrib.string then
								t_insert(args, child.attrib.string)
							elseif child.attrib.boolean then
								t_insert(args, child.attrib.boolean == "true")
							end
						end
					end
				end
				self:SetMode(node.attrib.mode, unpack(args))
			elseif node.elem == "Accounts" then
				self.lastAccountName = node.attrib.lastAccountName
				for _, child in ipairs(node) do
					if child.elem == "Account" then
						self.accountSessionIDs[child.attrib.accountName] = child.attrib.sessionID
					end
				end
			elseif node.elem == "Misc" then
				if node.attrib.buildSortMode then
					self.buildSortMode = node.attrib.buildSortMode
				end
			end
		end
	end
end

function main:SaveSettings()
	local setXML = { elem = "PathOfBuilding" }
	local mode = { elem = "Mode", attrib = { mode = self.mode } }
	for _, val in ipairs(self.modeArgs) do
		local child = { elem = "Arg", attrib = { } }
		if type(val) == "number" then
			child.attrib.number = tostring(val)
		elseif type(val) == "boolean" then
			child.attrib.boolean = tostring(val)
		else
			child.attrib.string = tostring(val)
		end
		t_insert(mode, child)
	end
	t_insert(setXML, mode)
	local accounts = { elem = "Accounts", attrib = { lastAccountName = self.lastAccountName } }
	for accountName, sessionID in pairs(self.accountSessionIDs) do
		t_insert(accounts, { elem = "Account", attrib = { accountName = accountName, sessionID = sessionID } })
	end
	t_insert(setXML, accounts)
	t_insert(setXML, { elem = "Misc", attrib = { buildSortMode = self.buildSortMode } })
	local res, errMsg = common.xml.SaveXMLFile(setXML, self.userPath.."Settings.xml")
	if not res then
		launch:ShowErrMsg("Error saving 'Settings.xml': %s", errMsg)
		return true
	end
end

function main:DrawBackground(viewPort)
	SetDrawLayer(nil, -100)
	SetDrawColor(0.5, 0.5, 0.5)
	DrawImage(self.tree.assets.Background1.handle, viewPort.x, viewPort.y, viewPort.width, viewPort.height, 0, 0, viewPort.width / 100, viewPort.height / 100)
	SetDrawLayer(nil, 0)
end

function main:DrawArrow(x, y, size, dir)
	local x1 = x - size / 2
	local x2 = x + size / 2
	local xMid = (x1 + x2) / 2
	local y1 = y - size / 2
	local y2 = y + size / 2
	local yMid = (y1 + y2) / 2
	if dir == "UP" then
		DrawImageQuad(nil, xMid, y1, xMid, y1, x2, y2, x1, y2)
	elseif dir == "RIGHT" then
		DrawImageQuad(nil, x1, y1, x2, yMid, x2, yMid, x1, y2)
	elseif dir == "DOWN" then
		DrawImageQuad(nil, x1, y1, x2, y1, xMid, y2, xMid, y2)
	elseif dir == "LEFT" then
		DrawImageQuad(nil, x1, yMid, x2, y1, x2, y2, x1, yMid)
	end
end

function main:DrawCheckMark(x, y, size)
	size = size / 0.8
	x = x - size / 2
	y = y - size / 2
	DrawImageQuad(nil, x + size * 0.15, y + size * 0.50, x + size * 0.30, y + size * 0.45, x + size * 0.50, y + size * 0.80, x + size * 0.40, y + size * 0.90)
	DrawImageQuad(nil, x + size * 0.40, y + size * 0.90, x + size * 0.35, y + size * 0.75, x + size * 0.80, y + size * 0.10, x + size * 0.90, y + size * 0.20)
end

function main:OpenPopup(width, height, title, controls, enterControl, defaultControl)
	local popup = common.New("PopupDialog", width, height, title, controls, enterControl, defaultControl)
	t_insert(self.popups, popup)
	return popup
end

function main:ClosePopup()
	t_remove(self.popups, 1)
end

function main:OpenMessagePopup(title, msg)
	local controls = { }
	local numMsgLines = 0
	for line in string.gmatch(msg .. "\n", "([^\n]*)\n") do
		t_insert(controls, common.New("LabelControl", nil, 0, 20 + numMsgLines * 16, 0, 16, line))
		numMsgLines = numMsgLines + 1
	end
	controls.close = common.New("ButtonControl", nil, 0, 40 + numMsgLines * 16, 80, 20, "Ok", function()
		main:ClosePopup()
	end)
	return self:OpenPopup(m_max(DrawStringWidth(16, "VAR", msg) + 30, 190), 70 + numMsgLines * 16, title, controls, "close")
end

function main:OpenConfirmPopup(title, msg, confirmLabel, onConfirm)
	local controls = { }
	local numMsgLines = 0
	for line in string.gmatch(msg .. "\n", "([^\n]*)\n") do
		t_insert(controls, common.New("LabelControl", nil, 0, 20 + numMsgLines * 16, 0, 16, line))
		numMsgLines = numMsgLines + 1
	end
	controls.confirm = common.New("ButtonControl", nil, -45, 40 + numMsgLines * 16, 80, 20, confirmLabel, function()
		onConfirm()
		main:ClosePopup()
	end)
	t_insert(controls, common.New("ButtonControl", nil, 45, 40 + numMsgLines * 16, 80, 20, "Cancel", function()
		main:ClosePopup()
	end))
	return self:OpenPopup(m_max(DrawStringWidth(16, "VAR", msg) + 30, 190), 70 + numMsgLines * 16, title, controls, "confirm")
end

function main:AddTooltipLine(size, text)
	for line in string.gmatch(text .. "\n", "([^\n]*)\n") do
		t_insert(self.tooltipLines, { size = size, text = line })
	end
end

function main:AddTooltipSeparator(size)
	t_insert(self.tooltipLines, { size = size })
end

function main:DrawTooltip(x, y, w, h, viewPort, col, center)
	local ttW, ttH = 0, 0
	for i, data in ipairs(self.tooltipLines) do
		if data.text or (self.tooltipLines[i - 1] and self.tooltipLines[i + 1] and self.tooltipLines[i + 1].text) then
			ttH = ttH + data.size + 2
		end
		if data.text then
			ttW = m_max(ttW, DrawStringWidth(data.size, "VAR", data.text))
		end
	end
	ttW = ttW + 12
	ttH = ttH + 10
	local ttX = x
	local ttY = y
	if w and h then
		ttX = ttX + w + 5
		if ttX + ttW > viewPort.x + viewPort.width then
			ttX = m_max(viewPort.x, x - 5 - ttW)
			if ttX + ttW > x then
				ttY = ttY + h
			end
		end
		if ttY + ttH > viewPort.y + viewPort.height then
			ttY = m_max(viewPort.y, y + h - ttH)
		end
	elseif center then
		ttX = m_floor(x - ttW/2)
	end
	col = col or { 0.5, 0.3, 0 }
	if type(col) == "string" then
		SetDrawColor(col) 
	else
		SetDrawColor(unpack(col))
	end
	DrawImage(nil, ttX, ttY, ttW, 3)
	DrawImage(nil, ttX, ttY, 3, ttH)
	DrawImage(nil, ttX, ttY + ttH - 3, ttW, 3)
	DrawImage(nil, ttX + ttW - 3, ttY, 3, ttH)
	SetDrawColor(0, 0, 0, 0.75)
	DrawImage(nil, ttX + 3, ttY + 3, ttW - 6, ttH - 6)
	SetDrawColor(1, 1, 1)
	local y = ttY + 6
	for i, data in ipairs(self.tooltipLines) do
		if data.text then
			if center then
				DrawString(ttX + ttW/2, y, "CENTER_X", data.size, "VAR", data.text)
			else
				DrawString(ttX + 6, y, "LEFT", data.size, "VAR", data.text)
			end
			y = y + data.size + 2
		elseif self.tooltipLines[i + 1] and self.tooltipLines[i - 1] and self.tooltipLines[i - 1].text then
			if type(col) == "string" then
				SetDrawColor(col) 
			else
				SetDrawColor(unpack(col))
			end
			DrawImage(nil, ttX + 3, y - 1 + data.size / 2, ttW - 6, 2)
			y = y + data.size + 2
		end
	end
	self.tooltipLines = wipeTable(self.tooltipLines)
end

return main