--import config.lua
--import utils.lua
--import uiLib.lua
--import romCompiler.lua

--import userConfig.lua

MAIN_VIEW = 1
TERMINAL_VIEW = 2
POLYEDITOR_VIEW = 3
LEVELEDITOR_VIEW = 4
ANIM_VIEW = 5
SYNTH_VIEW = 6
GAME_VIEW = 7
DEBUGLOG_VIEW = 8
MODELVISUALIZER_VIEW = 9
MEMORYVISUALIZER_VIEW = 10

USERPROGRAMS = 20

Main = {}
Main.lock = false
Main.views = {}
Main.activeView = POLYEDITOR_VIEW
Main.dragginTopBar = -1
Main.time = 0.0
Main.lastClickTime = 0.0
Main.doubleClicked = false
Main.mouseInView = -1
Main.subProgramErrorLogged = false
Main.gameRunning = false
Main.userPrograms = {}
Main.editingAssetIcon = nil

Main.lastTimeFPS = 0.0
Main.fps = 0

function _assetLimitCheck()
	Main.romCompileAllowed = true
	for pageId = 1, MEM_PAGES do
		local files = listFiles("page" .. pageId)
		if #files > ASSETS_PER_PAGE_MAX then
			print("Warning: page " .. pageId .. " asset limit exceeded (max " .. ASSETS_PER_PAGE_MAX .. "). Rom compilation disabled.")
			Main.romCompileAllowed = false
		end
	end
end

function start()
	_assetLimitCheck()
	Main.appConfig = {}
	local appConfigStr = loadFile("pw.cfg", -1)
	if appConfigStr ~= "" then
		Main.appConfig = Utils.deserializeTable(appConfigStr)
	end

	if Main.appConfig.projectName == nil then
		Main.appConfig.projectName = "project1"
	end

	setProject(Main.appConfig.projectName)

	Main.trashOpen = false
	Main.topBarLimitTop = 30.0
	Main.shiftKeyDown = -1
	Main.ctrlKeyDown = -1
	Main.mouseDownStartedInView = false
	Main.shortcuts = {}
	Main.capturingGif = false;

	Main.winSize = vec2(512, 512)

	setMessageCallback(Main._onAppMessage)

	UILib.init()

	Main.backgroundSprite = allocSprite()
	Main.backgroundSpriteData = {}
	Main.backgroundSpriteData.position = vec3(0.0, 0.0, -10.0)
	Main.backgroundSpriteData.size = vec2(512.0, 512.0)
	Main.backgroundSpriteData.color = vec4(255, 255, 255, 255)
	Main.backgroundSpriteData.textureId = TEXTURE_MAINBACKGROUND
	Main.backgroundSpriteData.texturePos = vec2(0.0, 0.0)
	Main.backgroundSpriteData.textureSize = vec2(512.0, 512.0)
	setSprite(Main.backgroundSprite, Main.backgroundSpriteData)

	Main.currentPageId = 1
	Main.currentAssetId = 1
	Main.assetRotLastTime =  0.0
	Main.draggingAssetIcon = nil

	Main._scanUserPrograms()

	Main._buildUI()

	Main._initAssetIcons()

	initProject()

	Main._makeView("sys/lua/programs/terminal.lua", TERMINAL_VIEW, Main.pwButton, 340, 320)

	Main._makeView("sys/lua/programs/debugLog.lua", DEBUGLOG_VIEW, nil, 340, 8 * 16)
	Main._debugLogView = Main.views[DEBUGLOG_VIEW]
	Main._debugLogView.persistent = true
	Main._debugLogView.position.z = UILib.uiDepth_overViews + 2
	Main._debugLogView.topBar.position.z = Main._debugLogView.position.z
	Main._debugLogView.closeButton.position.z = Main._debugLogView.position.z + 0.1
	Main._debugLogView.closeButton.callback = function()
		Main._showDebugLog(false)
	end

	Main._makeView("sys/lua/programs/modelVisualizer.lua", MODELVISUALIZER_VIEW, nil, 80, 80)
	Main._modelVisualizerView = Main.views[MODELVISUALIZER_VIEW]
	Main._modelVisualizerView.persistent = true
	Main._modelVisualizerView.position.z = UILib.uiDepth_overViews + 1
	Main._modelVisualizerView.topBar.position.z = Main._modelVisualizerView.position.z
	Main._modelVisualizerView.closeButton.position.z = Main._modelVisualizerView.position.z + 0.1
	Main._modelVisualizerView.closeButton.callback = function()
		Main._showModelVisualizer(false)
		UILib.setButtonPressed(Main.modelVisualizerButton, false)
	end
	local showModelVisualizer = getAppVar("modelVisualizerVisible")
	if showModelVisualizer == nil then
		showModelVisualizer = true
	end
	Main._showModelVisualizer(showModelVisualizer)

	Main._makeView("sys/lua/programs/memoryVisualizer.lua", MEMORYVISUALIZER_VIEW, nil, 8 * 10, 8 * 17 + 2)
	Main._memoryVisualizerView = Main.views[MEMORYVISUALIZER_VIEW]
	Main._memoryVisualizerView.persistent = true
	Main._memoryVisualizerView.position.z = UILib.uiDepth_overViews + 3
	Main._memoryVisualizerView.topBar.position.z = Main._memoryVisualizerView.position.z
	Main._memoryVisualizerView.closeButton.position.z = Main._memoryVisualizerView.position.z + 0.1
	Main._memoryVisualizerView.closeButton.callback = function()
		Main._toggleMemoryVisualizer()
	end
	local showMemoryVisualizer = getAppVar("memoryVisualizerVisible")
	if showMemoryVisualizer == nil then
		showMemoryVisualizer = true
	end
	Main._showMemoryVisualizer(showMemoryVisualizer)

	Main._makeView("sys/lua/programs/polyEditor.lua", POLYEDITOR_VIEW, Main.polyEditorButton, 340, 320, {"gltf"})
	Main._makeView("sys/lua/programs/levelEditor.lua", LEVELEDITOR_VIEW, Main.levelEditorButton, 340, 320, {"gltf", "lvl"})
	Main._makeView("sys/lua/programs/animEditor.lua", ANIM_VIEW, Main.animEditorButton, 340, 320, {"gltf", "anim"})
	Main._makeView("sys/lua/programs/gamePlayer.lua", GAME_VIEW, Main.gameButton, 320, 288)

	Main._enableView(TERMINAL_VIEW, true)

	Main._setCurrentPage(1)

	Main._registerShortcuts()
end

function Main._scanUserPrograms()
	local files = listFiles("extraPrograms")

	for i = 1, #files do
		local fileName = files[i]
		local luaFile = "extraPrograms/" .. fileName
		
		local content = loadFile(luaFile, PATH_FROM_APP)
		if content ~= "" then
			local userProgram = {}
			userProgram.name = replaceStr(fileName, ".lua", "")
			userProgram.path = luaFile
			userProgram.id = USERPROGRAMS + i

			Main.userPrograms[#Main.userPrograms + 1] = userProgram

			Main.views[userProgram.id] = Main._makeView(luaFile, userProgram.id, nil, 340.0, 320.0)
		end
	end
end

function Main._userProgramsButtonPressed()
	local menuTable = {}
	for i = 1, #Main.userPrograms do
		local program = Main.userPrograms[i]
		menuTable[#menuTable + 1] = {program.name, function()
			Main._enableView(program.id)
		end}
	end

	UILib.menuButton.colorBase = Config.uiColors.userProgramsMenu
	UILib.menuButton.colorHover = Config.uiColors.userProgramsMenu + 50.0
	
	UILib.openMenu(Main.userProgramsButton, menuTable, Config.uiColors.userProgramsMenu)
end

function Main._compileRom()
	if not Main.romCompileAllowed then
		print("[RomCompiler] Compilation blocked: asset limit exceeded")
		return
	end
	local compiled = RomCompiler.compile()
	if not compiled then
		print("[RomCompiler] Compilation failed")
		return
	end
	saveFile("game.pw", 0, compiled)
	print("[RomCompiler] Saved to game.pw (" .. #compiled .. " bytes)")
	
	Main.tooltipEndTime = getTime() + 3.0
	Main.tooltipSpriteData.position = vec3(Main.exportButton.position.x, Main.exportButton.position.y + Main.exportButton.size.y + 4.0, UILib.uiDepth_overViews + 100)
	Main.tooltipSpriteData.size = vec2(8.0 * 17, 10.0)
	Main.tooltipSpriteData.text = "saved rom game.pw"
	setSprite(Main.tooltipSprite, Main.tooltipSpriteData)

	openProjectDir()
end

function getCurrentProgram()
	local programId = MAIN_VIEW

	if Main.currentView ~= nil then
		programId = Main.currentView.id
	end

	if Main.gameRunning == true then
		programId = GAME_PROGRAMID
	end

	return programId
end

function Main._toggleModelVisualizer()
	local state = not Main._modelVisualizerView.enabled

	Main._showModelVisualizer(state)
end

function Main._toggleMemoryVisualizer()
	local state = not Main._memoryVisualizerView.enabled

	Main._showMemoryVisualizer(state)
end

function Main._showMemoryVisualizer(state)
	Main._memoryVisualizerView.enabled = state
	Main._memoryVisualizerView.topBar.visible = state
	Main._memoryVisualizerView.closeButton.visible = state

	local winSize = getWindowSize()
	if Main._memoryVisualizerView.position.x == 0.0 and Main._memoryVisualizerView.position.y == 0.0 then
		Main._memoryVisualizerView.position.x = winSize.x - Main._memoryVisualizerView.size.x - 4
		Main._memoryVisualizerView.position.y = 4 * 31
	end

	Main._memoryVisualizerView.topBar.position.x = Main._memoryVisualizerView.position.x
	Main._memoryVisualizerView.topBar.position.y = Main._memoryVisualizerView.position.y

	invokeProgram(Main._memoryVisualizerView.id, "programActive", state)

	UILib.setButtonPressed(Main.memoryVisualizerButton, state)

	setAppVar("memoryVisualizerVisible", state)
end

function Main._showModelVisualizer(state)
	if Main._modelVisualizerView.enabled == state then
		return
	end

	Main._modelVisualizerView.enabled = state
	Main._modelVisualizerView.topBar.visible = state
	Main._modelVisualizerView.closeButton.visible = state

	local winSize = getWindowSize()
	Main._modelVisualizerView.position.x = winSize.x - Main._modelVisualizerView.size.x - 4
	Main._modelVisualizerView.position.y = 8 * 4

	Main._modelVisualizerView.topBar.position.x = Main._modelVisualizerView.position.x
	Main._modelVisualizerView.topBar.position.y = Main._modelVisualizerView.position.y

	UILib.setButtonPressed(Main.modelVisualizerButton, state)

	invokeProgram(Main._modelVisualizerView.id, "programActive", state)

	setAppVar("modelVisualizerVisible", state)
end

function Main._showDebugLog(state)
	if Main._debugLogView == nil then
		return
	end
	
	if Main._debugLogView.enabled == state then
		return
	end

	Main._debugLogView.enabled = state
	Main._debugLogView.topBar.visible = state
	Main._debugLogView.closeButton.visible = state

	local winSize = getWindowSize()
	Main._debugLogView.position.x = (winSize.x * 0.5) - (Main._debugLogView.size.x * 0.5)
	Main._debugLogView.position.y = winSize.y - Main._debugLogView.size.y - 8.0

	if Main.currentView ~= nil then
		Main._debugLogView.position.x = Main.currentView.position.x
		Main._debugLogView.position.y = Main.currentView.position.y + (Main.currentView.size.y - Main._debugLogView.size.y)
	end

	Main._debugLogView.topBar.position.x = Main._debugLogView.position.x
	Main._debugLogView.topBar.position.y = Main._debugLogView.position.y
end

function setCapturingGif(state)
	Main.capturingGif = state

	if Main.capturingGif == true then
		Main._captureGif()
	end
end

function Main._reloadAssets()
	refreshAssetIcons()

	invokeProgram(POLYEDITOR_VIEW, "reloadTextures")
end

function Main._logError(message)
	if Main.subProgramErrorLogged == true then
		return
	end

	Main.subProgramErrorLogged = true

	Main._showDebugLog(true)

	local messageLog = message

	if Main.gameRunning == false then
		messageLog = "log saved to pw_errors.txt\n" .. message
	end

	invokeProgram(DEBUGLOG_VIEW, "sendText", messageLog)

	if Main.gameRunning == false then
		saveFile("pw_errors.txt", -1, message)
	end
end

function Main._logMessage(message)
	Main._showDebugLog(true)

	invokeProgram(DEBUGLOG_VIEW, "logMessage", message)
end

function Main._onAppMessage(message)
	if strStartsWith(message, "error:") == true then
		local errorMessage = replaceStr(message, "error:", "")
		Main._logError(errorMessage)

		if Main.gameRunning == true then
			invokeProgram(GAME_VIEW, "stopGame")
		end

	elseif strStartsWith(message, "print:") == true then
		local printMessage = replaceStr(message, "print:", "")
		Main._logMessage(printMessage)

	elseif message == "filesChanged" then
		if Main._memoryVisualizerView.enabled == true then
			invokeProgram(MEMORYVISUALIZER_VIEW, "refreshAssets")
		end

	elseif message == "windowFocused" then
		Main._reloadAssets()
		_assetLimitCheck()
	end
end

function registerShortcut(shortcutData)
	local shortcut = {}
	shortcut.programId = shortcutData.programId
	shortcut.desc = shortcutData.desc
	shortcut.key = shortcutData.key
	shortcut.modifier = shortcutData.modifier
	shortcut.callback = shortcutData.callback
	
	local shortcuts = Main.shortcuts[shortcut.programId]

	if shortcuts == nil then
		shortcuts = {}
		Main.shortcuts[shortcutData.programId] = shortcuts
	end

	shortcuts[#shortcuts + 1] = shortcut
end

function Main._registerShortcuts()
	registerShortcut({desc = "save", key = KEY_S, modifier = KEY_LEFT_CONTROL, callback = "saveAll", programId = MAIN_VIEW})
	registerShortcut({desc = "save", key = KEY_S, modifier = KEY_LEFT_SUPER, callback = "saveAll", programId = MAIN_VIEW})
	registerShortcut({desc = "delete asset", key = KEY_BACKSPACE, callback = "deleteSelectedAssetShortcut", programId = MAIN_VIEW})
	registerShortcut({desc = "delete asset", key = KEY_DELETE, callback = "deleteSelectedAssetShortcut", programId = MAIN_VIEW})
	registerShortcut({desc = "save", key = KEY_ESCAPE, callback = "exitFullScreenShortcut", programId = MAIN_VIEW})
end

function exitFullScreenShortcut()
	if Main.mouseInView == MAIN_VIEW or 
		Main.mouseInView == TERMINAL_VIEW or 
		Main.mouseInView == MODELVISUALIZER_VIEW or
		Main.mouseInView == MEMORYVISUALIZER_VIEW then

		if getWindowFullScreen() == true then
			exitFullScreen()
		end
	end
end

function deleteSelectedAssetShortcut()
	if Main.mouseInView ~= MAIN_VIEW then
		return
	end

	deleteSelectedAsset()
end

function deleteSelectedAsset()
	for i = 1, #Main.assetIcons do
		local assetIcon = Main.assetIcons[i]
		if assetIcon.selected == true and assetIcon.visible == true then
			Main.projectData.pages[Main.currentPageId].assetIconsPos[assetIcon.name] = nil
			moveFile(
				"page" .. Main.currentPageId .. "/" .. assetIcon.name .. "." .. assetIcon.fileType,
				"trash/" .. assetIcon.name .. "." .. assetIcon.fileType)
			refreshAssetIcons()
			return
		end
	end
end

function setViewTitle(viewId, title)
	local view = Main.views[viewId]
	if view ~= nil then
		view.topBar.text = title
		view.topBar.changed = true
	end
end

function Main._buildUI()
	Main.topButtons = {}

	Main.pwButton = UILib.buttonIcon(
		vec3(4.0, 4.0, UILib.uiDepth_mainButtons), vec2(16.0, 16.0), 
		Config.uiColors.viewButton,
		vec2(0.0, 0.0), vec2(16.0, 16.0),
		vec2(8.0 * 10.0, 8.0 * 2.0), vec2(16.0, 16.0),
		Config.uiColors.icon, function() Main._enableView(TERMINAL_VIEW) end)
	Main.pwButton.roundCorner = 0.3
	Main.pwButton.desc = "terminal"
	Main.topButtons[TERMINAL_VIEW] = Main.pwButton

	Main.polyEditorButton = UILib.buttonIcon(
		vec3(4.0 * 6.0, 4.0, UILib.uiDepth_mainButtons), vec2(16.0, 16.0), 
		Config.uiColors.viewButton,
		vec2(0.0, 0.0), vec2(16.0, 16.0),
		vec2(0.0, 8.0 * 2), vec2(16.0, 16.0),
		Config.uiColors.icon, function() Main._enableView(POLYEDITOR_VIEW) end)
	Main.polyEditorButton.roundCorner = 0.3
	Main.polyEditorButton.desc = "polyEditor"
	Main.topButtons[POLYEDITOR_VIEW] = Main.polyEditorButton

	Main.levelEditorButton = UILib.buttonIcon(
		vec3(4.0 * 11.0, 4.0,UILib.uiDepth_mainButtons), vec2(16.0, 16.0), 
		Config.uiColors.viewButton,
		vec2(0.0, 0.0), vec2(16.0, 16.0),
		vec2(8.0 * 4.0, 8.0 * 2), vec2(16.0, 16.0),
		Config.uiColors.icon, function() Main._enableView(LEVELEDITOR_VIEW) end)
	Main.levelEditorButton.roundCorner = 0.3
	Main.levelEditorButton.desc = "levelEditor"
	Main.topButtons[LEVELEDITOR_VIEW] = Main.levelEditorButton

	Main.animEditorButton = UILib.buttonIcon(
		vec3(4.0 * 16.0, 4.0, UILib.uiDepth_mainButtons), vec2(16.0, 16.0), 
		Config.uiColors.viewButton,
		vec2(0.0, 0.0), vec2(16.0, 16.0),
		vec2(8.0 * 2.0, 8.0 * 2), vec2(16.0, 16.0),
		Config.uiColors.icon, function() Main._enableView(ANIM_VIEW) end)
	Main.animEditorButton.roundCorner = 0.3
	Main.animEditorButton.desc = "animEditor"
	Main.topButtons[ANIM_VIEW] = Main.animEditorButton

	Main.gameButton = UILib.buttonIcon(
		vec3(4.0 * 21.0, 4.0, UILib.uiDepth_mainButtons), vec2(16.0, 16.0), 
		Config.uiColors.viewButton,
		vec2(0.0, 0.0), vec2(16.0, 16.0),
		vec2(8.0 * 6.0, 8.0 * 2), vec2(16.0, 16.0),
		Config.uiColors.icon, function() Main._enableView(GAME_VIEW) end)
	Main.gameButton.roundCorner = 0.3
	Main.gameButton.desc = "gamePlayer"
	Main.topButtons[GAME_VIEW] = Main.gameButton

	local lastViewButton = Main.gameButton

	if #Main.userPrograms > 0 then
		Main.userProgramsButton = UILib.buttonIcon(
			vec3(4.0 * 26.0, 4.0, UILib.uiDepth_mainButtons), vec2(16.0, 16.0),
			Config.uiColors.viewButton,
			vec2(0.0, 0.0), vec2(16.0, 16.0),
			vec2(8.0 * 9.0, 8.0 * 6.0), vec2(16.0, 16.0),
			Config.uiColors.icon, function() Main._userProgramsButtonPressed() end)
		Main.userProgramsButton.roundCorner = 0.3
		Main.userProgramsButton.desc = "extra programs"
		lastViewButton = Main.userProgramsButton
	end

	Main.pageButtons = {}

	for i = 1, 4  do
		local sizeX = 8.0 * 6.0

		local pageButton1 = UILib.button("page" .. i,
			vec3(lastViewButton.position.x + lastViewButton.size.x + 8.0 + ((sizeX + 1) * (i - 1)), 4.0, UILib.uiDepth_mainButtons),
			vec2(sizeX, 8.0),
			Config.uiColors.pageButton,
			function() Main._setCurrentPage(i) end)

		Main.pageButtons[i] = pageButton1

		local pageButton2 = UILib.button("page" .. (i + 4),
			vec3(lastViewButton.position.x + lastViewButton.size.x + 8.0 + ((sizeX + 1) * (i - 1)), 12.0 + 1.0, UILib.uiDepth_mainButtons),
			vec2(sizeX, 8.0),
			Config.uiColors.pageButton,
			function() Main._setCurrentPage(i + 4) end)

		Main.pageButtons[i + 4] = pageButton2
	end

	local space = lastViewButton.position.x + lastViewButton.size.x + 16.0 + ((8.0 * 6.0) * 4) + 2.0

	Main.exportButton = UILib.buttonIcon(
		vec3(space, 4.0, UILib.uiDepth_mainButtons), vec2(16.0, 16.0), 
		Config.uiColors.downloadButton,
		vec2(0.0, 0.0), vec2(16.0, 16.0),
		vec2(8.0 * 14.0, 8.0 * 2), vec2(16.0, 16.0),
		Config.uiColors.icon, function() Main._compileRom() end)
	Main.exportButton.desc = "compile game rom"
	Main.exportButton.roundCorner = 0.3

	Main.tooltipSprite = allocSprite()
	Main.tooltipSpriteData = {}
	Main.tooltipSpriteData.position = vec3(Main.exportButton.position.x, Main.exportButton.position.y + Main.exportButton.size.y + 4.0, UILib.uiDepth_overViews + 100)
	Main.tooltipSpriteData.size = vec2(8.0 * 17, 10.0)
	Main.tooltipSpriteData.color = Config.uiColors.mainTooltip
	Main.tooltipSpriteData.textColor = Config.uiColors.mainTooltipText
	Main.tooltipSpriteData.text = "saved rom game.pw"
	setSprite(Main.tooltipSprite, Main.tooltipSpriteData)
	Main.tooltipEndTime = 0

	Main.trashButton = UILib.buttonIcon(
		vec3(space + 16.0 + 8.0, 4.0, UILib.uiDepth_mainButtons), vec2(16.0, 16.0), 
		Config.uiColors.trashButton,
		vec2(0.0, 0.0), vec2(16.0, 16.0),
		vec2(8.0 * 12.0, 8.0 * 2), vec2(16.0, 16.0),
		Config.uiColors.icon, Main._trashButtonPressed)
	Main.trashButton.roundCorner = 0.3
	Main.trashButton.desc = "recycle"

	Main.trashPanelSprite = allocSprite()
	Main.trashPanel = {}
	Main.trashPanel.position = vec3(Main.trashButton.position.x, Main.trashButton.position.y + Main.trashButton.size.y + 4.0, UILib.uiDepth_overViews + 10.0)
	Main.trashPanel.size = vec2(40.0 * 3, 32.0 * 6)
	Main.trashPanel.color = Config.uiColors.panel

	setSprite(Main.trashPanelSprite, Main.trashPanel)

	Main.modelVisualizerButton = UILib.button("V",
		vec3(512 - 12 - 8 - 4, 4.0, UILib.uiDepth_mainButtons), vec2(8.0, 8.0), 
		Config.uiColors.panelLight, Main._toggleModelVisualizer)
	Main.modelVisualizerButton.roundCorner = 0.8
	Main.modelVisualizerButton.desc = "viewer"

	Main.memoryVisualizerButton = UILib.button("M",
		vec3(512 - 12, 4.0, UILib.uiDepth_mainButtons), vec2(8.0, 8.0), 
		Config.uiColors.panelLight, Main._toggleMemoryVisualizer)
	Main.memoryVisualizerButton.roundCorner = 0.8
	Main.memoryVisualizerButton.desc = "memory"

	Main.exitFullScreenButton = UILib.button("full screen",
		vec3(Main.memoryVisualizerButton.position.x - (8.0 * 12) - 8.0, 4.0, UILib.uiDepth_mainButtons),
		vec2(8.0 * 11, 8.0),
		Config.uiColors.panelLight, function() exitFullScreen() end)
	Main.exitFullScreenButton.visible = false
end

function getCurrentPage()
	return Main.currentPageId
end

function Main._setCurrentPage(pageId)
	UILib.setButtonPressed(Main.pageButtons[Main.currentPageId], false)

	Main.currentPageId = pageId

	UILib.setButtonPressed(Main.pageButtons[Main.currentPageId], true)
	
	invokeProgram(POLYEDITOR_VIEW, "setPage", pageId)
	invokeProgram(GAME_VIEW, "setPage", pageId)
	invokeProgram(LEVELEDITOR_VIEW, "setPage", pageId)
	invokeProgram(ANIM_VIEW, "setPage", pageId)

	refreshAssetIcons()
end

function initProject()
	Main.projectData = {}

	-- reloadProjectFile reads project.cfg into Main.projectData, so the pages/
	-- palette migration below must run AFTER it or it would be discarded.
	reloadProjectFile()

	if Main.projectData.pages == nil then
		Main.projectData.pages = {}

		for i = 1, MEM_PAGES do
			local pageData = {}
			Main.projectData.pages[i] = pageData
		end
	end

	for i = 1, MEM_PAGES do
		local pageData = Main.projectData.pages[i]

		if pageData.assetIconsPos == nil then
			pageData.assetIconsPos = {}
		end

	end

	if Main.projectData.palette == nil then
		if Main.projectData.pages[1] ~= nil and Main.projectData.pages[1].palette ~= nil then
			Main.projectData.palette = Main.projectData.pages[1].palette
		else
			Main.projectData.palette = {}
			for i = 1, 24 do
				local col = Config.palette[i]
				Main.projectData.palette[i] = { col.x, col.y, col.z, col.w }
			end
		end
	end

	for i = 1, MEM_PAGES do
		Main.projectData.pages[i].palette = nil
	end

	refreshAssetIcons()

	reloadTextures()

	invokeProgram(POLYEDITOR_VIEW, "clearProgram")
	invokeProgram(ANIM_VIEW, "clearProgram")
	invokeProgram(LEVELEDITOR_VIEW, "clearProgram")

	-- re-sync the poly editor's texture handles/byte cache to the newly opened
	-- project, matching the windowFocused refresh path (Main._reloadAssets).
	-- Must run before _setCurrentPage below so setPage's _abortCurrentTool writes
	-- back the new project's bytes rather than the previous project's stale cache.
	invokeProgram(POLYEDITOR_VIEW, "reloadTextures")

	-- reset the active page to 1 for the newly opened project (updates the page
	-- highlight and all views consistently)
	Main._setCurrentPage(1)

	setGameRunning(false)
end

function reloadTextures()
	for i = 1, MEM_PAGES do
		reloadTexture(i)
	end
end

function refreshPalette()
	local colors = {}
	for i = 1, 24 do
		local c = Main.projectData.palette[i]
		colors[i] = vec4(c[1], c[2], c[3], c[4])
	end
	setDitherEnabled(true, colors)
end

function getProjectPalette()
	return Main.projectData.palette
end

function setProjectPalette(palette)
	Main.projectData.palette = palette

	if Main.projectData.video_ditherEnabled == true then
		refreshPalette()
	end

	Main._saveProject()
end

function Main._findNewAssetIconSpot()
	for x = 0, 14 do
		for y = 1, 14 do
			local emptySpot = true

			for i = 1, #Main.assetIcons do
				local assetIcon = Main.assetIcons[i]

				if assetIcon.gridPosX == x and assetIcon.gridPosY == y then
					emptySpot = false

					break
				end
			end

			if emptySpot == true then
				return {x, y}
			end
		end
	end

	return {1, 1}
end

function refreshAssetIcons()
	local assetNames = listFiles("page" .. Main.currentPageId)
	
	for assetIconIt = 1, #Main.assetIcons do
		local assetIcon = Main.assetIcons[assetIconIt]
		assetIcon.visible = false
		assetIcon.selected = false
		assetIcon.name = ""
		assetIcon.label.test = ""
		assetIcon.gridPosX = -1
		assetIcon.gridPosY = -1
	end

	local projectChanged = false

	Main.assetIconsByName = {}
	
	for i = 1, #assetNames do
		local assetName = assetNames[i]
		local tokens = splitStr(assetName, ".")
		local name = tokens[1]
		local fileType = tokens[2]
		
		if (fileType == "gltf" or fileType == "lvl" or fileType == "anim") and name ~= "tmp" then
			local assetIcon = Main.assetIcons[i]

			assetIcon.name = name

			if fileType == "gltf" then
				assetIcon.button.iconSpriteData.texturePos = vec2(0.0, 8.0 * 2)
			elseif fileType == "lvl" then
				assetIcon.button.iconSpriteData.texturePos = vec2(8.0 * 4, 8.0 * 2)
			elseif fileType == "anim" then
				assetIcon.button.iconSpriteData.texturePos = vec2(8.0 * 2, 8.0 * 2)
			end

			local nameRow1 = name:sub(1, 4)

			assetIcon.label.text = nameRow1

			local nameRow2 = ""

			if #name > 4 then
				nameRow2 = name:sub(5, 8)
			end

			assetIcon.visible = true
			assetIcon.name = name
			assetIcon.fileType = fileType

			assetIcon.label2.text = nameRow2

			Main.assetIconsByName[name] = Main.assetIcons[i]

			local assetIconPos = Main.projectData.pages[Main.currentPageId].assetIconsPos[name]
			
			if assetIconPos == nil or #assetIconPos < 2 then
				assetIconPos = Main._findNewAssetIconSpot()
				Main.projectData.pages[Main.currentPageId].assetIconsPos[name] = assetIconPos

				 projectChanged = true
			end
			
			Main._setAssetIconGridPos(assetIcon, assetIconPos[1], assetIconPos[2])
		end
	end

	for k, v in pairs(Main.projectData.pages[Main.currentPageId].assetIconsPos) do
		if Main.assetIconsByName[k] == nil then
			Main.projectData.pages[Main.currentPageId].assetIconsPos[k] = nil
		end
	end

	Main._resolveAssetIconOverlaps()

	if projectChanged == true then
		Main._saveProject()
	end
end

function refreshModelVisualizer(filename)
	invokeProgram(MODELVISUALIZER_VIEW, "refreshModel", filename, Main.currentPageId)
end

function Main._saveProject()
	local dataStr = Utils.serializeTable(Main.projectData)
	saveFile("project.cfg", 0, dataStr)
end

function saveAppConfig()
	Main.appConfig.projectName = getProjectName()
	local dataStr = Utils.serializeTable(Main.appConfig)
	saveFile("pw.cfg", -1, dataStr)
end

function setAppVar(varName, value)
	Main.appConfig[varName] = value

	saveAppConfig()
end

function getAppVar(varName)
	return Main.appConfig[varName]
end

function getProjectVar(varName)
	return Main.projectData[varName]
end

function setProjectVar(varName, value)
	Main.projectData[varName] = value
	Main._saveProject()
	reloadProjectFile()
end

function reloadProjectFile()
	local configStr = loadFile("project.cfg", 0)
	if configStr ~= "" then
		Main.projectData = Utils.deserializeTable(configStr)
	end

	if Main.projectData.video_ditherEnabled == nil then
		Main.projectData.video_ditherEnabled = false
	end
	if Main.projectData.video_ditherEnabled == true and Main.projectData.palette ~= nil then
		refreshPalette()
	end

	if Main.projectData.video_bilinearInterpEnabled == nil then
		Main.projectData.video_bilinearInterpEnabled = false
	end

	if Main.projectData.video_ditherTransparencyEnabled == nil then
		Main.projectData.video_ditherTransparencyEnabled = false
	end

	setDitherTransparencyEnabled(Main.projectData.video_ditherTransparencyEnabled)

	if Main.projectData.video_colorCompressionEnabled == nil then
		Main.projectData.video_colorCompressionEnabled = false
	end

	setColorCompressionEnabled(Main.projectData.video_colorCompressionEnabled)
	
	if Main._modelVisualizerView ~= nil and Main._modelVisualizerView.enabled == true then
		-- refresh video options for modelVisualizer
		invokeProgram(Main._modelVisualizerView.id, "programActive", true)
	end
end

function saveAll()
	local dataStr = Utils.serializeTable(Main.projectData)
	saveFile("project.cfg", 0, dataStr)

	if Main.currentView ~= nil then
		invokeProgram(Main.currentView.id, "save")
	end

	for i = 1, MEM_PAGES do
		saveTexture(i)
	end
end

function Main._trashButtonPressed()
	Main.trashOpen = not Main.trashOpen

	if Main.trashOpen == false then
		return
	end

	for i = 1, #Main.trashAssetIcons do
		Main.trashAssetIcons[i].visible = false
	end

	local trashAssets = listFiles("trash")
	
	local itX = 0
	local itY = 0
	for i = 1, #trashAssets do
		local assetName = trashAssets[i]

		local tokens = splitStr(assetName, ".")
		local name = tokens[1]
		local fileType = tokens[2]

		local assetIcon = Main.trashAssetIcons[i]

		assetIcon.visible = true

		local nameRow1 = name:sub(1, 4)

		local nameRow2 = ""

		if #name > 4 then
			nameRow2 = name:sub(5, 8)
		end
		
		assetIcon.label.text = nameRow1
		assetIcon.label2.text = nameRow2

		assetIcon.name = name
		assetIcon.fileType = fileType

		if fileType == "gltf" then
			assetIcon.button.iconSpriteData.texturePos = vec2(0.0, 8.0 * 2)
		elseif fileType == "lvl" then
			assetIcon.button.iconSpriteData.texturePos = vec2(8.0 * 4, 8.0 * 2)
		elseif fileType == "anim" then
			assetIcon.button.iconSpriteData.texturePos = vec2(8.0 * 2, 8.0 * 2)
		end

		local offsetX = Main.gridSize * itX
		local offsetY = Main.gridSize * itY

		itY = itY + 1

		if itY > 4 then
			itY = 0
			itX = itX + 1
		end
		
		local px = Main.trashPanel.position.x + 10.0 + offsetX
		local py = Main.trashPanel.position.y + 4.0 + offsetY

		assetIcon.inTrash = true

		Main._setAssetIconPos(assetIcon, px, py)
	end
end

-- function setModelPreview(layerId, mesh)
-- 	Main.assetPreview.mesh = mesh
-- end

function Main._prettyPrint(tbl, indent)
  if not indent then indent = 0 end
  for k, v in pairs(tbl) do
    formatting = string.rep("  ", indent) .. k .. ": "
    if type(v) == "table" then
      print(formatting)
      Main._prettyPrint(v, indent+1)
    elseif type(v) == 'boolean' then
      print(formatting .. tostring(v))      
    else
      print(formatting .. v)
    end
  end
end

function Main._openViewFromAsset(assetName, assetFileType)
	if assetFileType == "gltf" then
		if Main.currentView == nil or Main.currentView.id == TERMINAL_VIEW or Main.currentView.id == GAME_VIEW then
			Main._enableView(POLYEDITOR_VIEW)

			invokeProgram(POLYEDITOR_VIEW, "openAsset", assetName .. "." .. assetFileType)

			return
		end
	end

	if Main.currentView ~= nil then
		for i = 1, #Main.currentView.openFileTypes do
			if Main.currentView.openFileTypes[i] == assetFileType then
				invokeProgram(Main.currentView.id, "openAsset", assetName .. "." .. assetFileType)

				return
			end
		end
	end

	for viewName, view in pairs(Main.views) do
		for i = 1, #view.openFileTypes do
			if view.openFileTypes[i] == assetFileType then
				Main._enableView(viewName)

				invokeProgram(view.id, "openAsset", assetName .. "." .. assetFileType)

				return
			end
		end
	end
end

function Main._assetIconPressed(assetIcon)
	Main.draggingAssetIcon = assetIcon
	Main.draggingAssetIconPos = vec3(assetIcon.button.position.x, assetIcon.button.position.y, assetIcon.button.position.z)
	Main.draggingAssetIconOrigGridPos = {assetIcon.gridPosX, assetIcon.gridPosY}
	Main.draggingAssetIconPageId = Main.currentPageId
	Main.draggingAssetIconName = assetIcon.name
	Main.draggingAssetIconFileType = assetIcon.fileType

	Main.mouseClickOffset = vec2(Main.draggingAssetIconPos.x, Main.draggingAssetIconPos.y) - Main.winMousePos

	Main._selectAssetIcon(assetIcon)

	if Main.doubleClicked == true then
		Main.draggingAssetIcon = nil

		Main._openViewFromAsset(assetIcon.name, assetIcon.fileType)
	end
end

function Main._assetIconRenamed(assetIcon)
	local oldName = "page" .. Main.currentPageId .. "/" .. assetIcon.name .. "." .. assetIcon.fileType
	local newName = "page" .. Main.currentPageId .. "/" .. assetIcon.label.text .. "." .. assetIcon.fileType
	
	if newName ~= oldName then
		moveFile(oldName, newName)

		local files = listFiles("page" .. Main.currentPageId)
		for i = 1, #files do
			local file = "page" .. Main.currentPageId .. "/" .. files[i]
			
			if file == newName then

				local name = assetIcon.label.text
				
				assetIcon.name = name
				
				local nameRow1 = name:sub(1, 4)

				assetIcon.label.text = nameRow1

				local nameRow2 = ""

				if #name > 4 then
					nameRow2 = name:sub(5, 8)
				end
				
				assetIcon.label2.text = nameRow2

				assetIcon.label.changed = true
				assetIcon.label2.changed = true

				break
			end
		end

		for viewName, view in pairs(Main.views) do
			invokeProgram(viewName, "renamedAsset", oldName, newName)
		end
	end
end

function Main._setAssetIconPos(assetIcon, px, py)
	assetIcon.button.position.x = px
	assetIcon.button.position.y = py
	
	assetIcon.button.iconSpriteData.position.x = px + 6.0
	assetIcon.button.iconSpriteData.position.y = py + 0.0
	
	assetIcon.label.position.x = px - (((8.0 * 4) - 32.0) / 2)
	assetIcon.label.position.y = py + 16.0

	assetIcon.label2.position.x = assetIcon.label.position.x
	assetIcon.label2.position.y = assetIcon.label.position.y + 8.0

	assetIcon.gridPosX = round(px / Main.gridSize)
	assetIcon.gridPosY = round(py / Main.gridSize)

	if assetIcon.trash ~= true then
		Main.projectData.pages[Main.currentPageId].assetIconsPos[assetIcon.name] = {assetIcon.gridPosX, assetIcon.gridPosY}
	end

	if assetIcon.inTrash ~= true then
		assetIcon.button.position.z = UILib.uiDepth_mainButtons;
		assetIcon.button.iconSpriteData.position.z = UILib.uiDepth_mainButtons + 0.1;
		assetIcon.label.position.z = UILib.uiDepth_mainButtons;
		assetIcon.label2.position.z = assetIcon.label.position.z
	else
		assetIcon.button.position.z = Main.trashPanel.position.z + 0.1
		assetIcon.button.iconSpriteData.position.z = assetIcon.button.position.z + 0.1
		assetIcon.label.position.z = Main.trashPanel.position.z + 0.1
		assetIcon.label2.position.z = assetIcon.label.position.z
	end

	if Main.draggingAssetIcon == assetIcon then
		assetIcon.button.position.z = UILib.uiDepth_cursor - 1
		assetIcon.button.iconSpriteData.position.z = UILib.uiDepth_cursor - 1 + 0.1
		assetIcon.label.position.z = UILib.uiDepth_cursor - 1
		assetIcon.label2.position.z = UILib.uiDepth_cursor - 1
	end

	setSprite(assetIcon.button.sprite, assetIcon.button)
	setSprite(assetIcon.button.iconSprite, assetIcon.button.iconSpriteData)
	setSprite(assetIcon.label.sprite, assetIcon.label)
	setSprite(assetIcon.label2.sprite, assetIcon.label2)
end

function Main._setAssetIconGridPos(assetIcon, tilePosX, tilePosY)
	local px = (Main.gridSize * tilePosX) + 4
	local py = (Main.gridSize * tilePosY) + 8.0 - 22

	Main._setAssetIconPos(assetIcon, px, py)
end

function Main._resolveAssetIconOverlaps()
	for i = 1, #Main.assetIcons do
		local assetIcon = Main.assetIcons[i]
		if assetIcon.visible == true then
			local overlapping = false
			for j = 1, #Main.assetIcons do
				local other = Main.assetIcons[j]
				if i ~= j and other.visible == true and other.gridPosX == assetIcon.gridPosX and other.gridPosY == assetIcon.gridPosY then
					overlapping = true
					break
				end
			end
			if overlapping == true then
				local spot = Main._findNewAssetIconSpot()
				Main._setAssetIconGridPos(assetIcon, spot[1], spot[2])
			end
		end
	end
end

function Main._selectAssetIcon(assetIcon)
	for i = 1, #Main.assetIcons do
		local asset = Main.assetIcons[i]
		asset.selected = false
	end

	assetIcon.selected = true
end

function selectAsset(filename)
	local tokens = splitStr(filename, ".")
	local name = tokens[1]

	for i = 1, #Main.assetIcons do
		local asset = Main.assetIcons[i]
		if asset.visible == true and asset.name == name then
			Main._selectAssetIcon(asset)
			return
		end
	end
end

function Main._updateDraggingAssetIcon()
	local assetIcon = Main.draggingAssetIcon

	local dragDelta = vec2Length((Main.winMousePos + Main.mouseClickOffset) - vec2(Main.draggingAssetIconPos.x, Main.draggingAssetIconPos.y))
	
	if Main.trashOpen == true then
		Main.trashOpen = false
	end

	if Main.trashButton.hovered == true then
		assetIcon.visible = false
	else
		assetIcon.visible = true		
	end

	Main.draggingAssetIconPos.x = (Main.winMousePos.x + Main.mouseClickOffset.x) - 8.0
	Main.draggingAssetIconPos.y = (Main.winMousePos.y + Main.mouseClickOffset.y) + 8.0

	local snapPosX = clamp(round(Main.draggingAssetIconPos.x / Main.gridSize), 0, 12)
	local snapPosY = clamp(round(Main.draggingAssetIconPos.y / Main.gridSize), 1, 12)

	Main._setAssetIconGridPos(assetIcon, snapPosX, snapPosY)

	assetIcon.button.hoverEnabled = false
	assetIcon.label.hoverEnabled = false
	assetIcon.label2.hoverEnabled = false

	local dragColor = Config.uiColors.file + 100
	if Main.currentView ~= nil then
		local insideView = intersectRect(Main.winMousePos, vec2(Main.currentView.position.x, Main.currentView.position.y), Main.currentView.size)
		if insideView == true then
			dragColor = Config.uiColors.file + 150
		end
	end

	assetIcon.button.color = dragColor
	assetIcon.button.colorBase = dragColor

	assetIcon.label.color = dragColor
	assetIcon.label.colorBase = dragColor

	assetIcon.label2.color = dragColor
	assetIcon.label2.colorBase = dragColor
	
	if dragDelta > 12.0 then
		Main.trashButton.colorBase = Config.paletteNames.orange
		Main.trashButton.colorHover = Config.paletteNames.red
		Main.trashButton.changed = true
	end
		
	if Main.mouseButtonReleased == true then
		Main.draggingAssetIcon = nil

		assetIcon.button.position.z = UILib.uiDepth_mainButtons
		assetIcon.button.iconSpriteData.position.z = UILib.uiDepth_mainButtons + 0.1
		assetIcon.label.position.z = UILib.uiDepth_mainButtons
		assetIcon.label2.position.z = UILib.uiDepth_mainButtons

		assetIcon.button.color = Config.uiColors.file
		assetIcon.button.colorBase = Config.uiColors.file
		assetIcon.button.hovered = false

		assetIcon.label.color = Config.uiColors.file
		assetIcon.label.colorBase = Config.uiColors.file
		assetIcon.label.hovered = false

		assetIcon.label2.color = Config.uiColors.file
		assetIcon.label2.colorBase = Config.uiColors.file
		assetIcon.label2.hovered = false

		assetIcon.button.hoverEnabled = true
		assetIcon.label.hoverEnabled = true
		assetIcon.label2.hoverEnabled = true

		setSprite(assetIcon.button.sprite, assetIcon.button)
		setSprite(assetIcon.label.sprite, assetIcon.label)
		setSprite(assetIcon.label2.sprite, assetIcon.label2)

		Main.trashButton.colorBase = Config.uiColors.trashButton
		Main.trashButton.colorHover = Config.paletteNames.red + 50
		Main.trashButton.changed = true

		if assetIcon.trash == true and Main.trashButton.hovered == false then
			local dropGridX = assetIcon.gridPosX
			local dropGridY = assetIcon.gridPosY

			moveFile(
				"trash/" .. assetIcon.name .. "." .. assetIcon.fileType,
				"page" .. Main.currentPageId .. "/" .. assetIcon.name .. "." .. assetIcon.fileType)

			assetIcon.inTrash = false

			refreshAssetIcons()

			local restoredIcon = Main.assetIconsByName[assetIcon.name]
			if restoredIcon ~= nil then
				local occupied = false
				for i = 1, #Main.assetIcons do
					local other = Main.assetIcons[i]
					if other ~= restoredIcon and other.visible == true and other.gridPosX == dropGridX and other.gridPosY == dropGridY then
						occupied = true
						break
					end
				end
				if not occupied then
					Main._setAssetIconGridPos(restoredIcon, dropGridX, dropGridY)
				end
			end

		elseif assetIcon.trash == nil and Main.trashButton.hovered == true then
			Main.projectData.pages[Main.currentPageId].assetIconsPos[assetIcon.name] = nil
			moveFile(
				"page" .. Main.currentPageId .. "/" .. assetIcon.name .. "." .. assetIcon.fileType,
				"trash/" .. assetIcon.name .. "." .. assetIcon.fileType)

			refreshAssetIcons()
		end

		if Main.currentView ~= nil then
			local insideView = intersectRect(Main.winMousePos, vec2(Main.currentView.position.x, Main.currentView.position.y), Main.currentView.size)
			if insideView == true then
				Main._setAssetIconGridPos(assetIcon, Main.draggingAssetIconOrigGridPos[1], Main.draggingAssetIconOrigGridPos[2])

				for i = 1, #Main.currentView.openFileTypes do
					if Main.currentView.openFileTypes[i] == assetIcon.fileType then
						local assetFileName = assetIcon.name .. "." .. assetIcon.fileType
						invokeProgram(Main.currentView.id, "openAsset", assetFileName)
						break
					end
				end
			end
		end

		for i = 1, #Main.pageButtons do
			if Main.pageButtons[i].hovered == true and i ~= Main.draggingAssetIconPageId then
				local srcFile = "page" .. Main.draggingAssetIconPageId .. "/" .. Main.draggingAssetIconName .. "." .. Main.draggingAssetIconFileType
				local dstFile = "page" .. i .. "/" .. Main.draggingAssetIconName .. "." .. Main.draggingAssetIconFileType

				moveFile(srcFile, dstFile)

				Main.projectData.pages[Main.draggingAssetIconPageId].assetIconsPos[Main.draggingAssetIconName] = nil

				Main._setCurrentPage(i)
				break
			end
		end

		Main._resolveAssetIconOverlaps()

		Main._saveProject()
	end
end

function _doneEditingAssetIcon(icon)
	Main.editingAssetIcon = nil

	local name = icon.name

	local nameRow1 = name:sub(1, 4)

	icon.label.text = nameRow1

	local nameRow2 = ""

	if #name > 4 then
		nameRow2 = name:sub(5, 8)
	end
	
	icon.label2.text = nameRow2

	icon.label.changed = true
	icon.label2.changed = true
end

function Main._addAssetIcon()
	local icon =  {}
	icon.visible = false
	icon.selected = false
	icon.name = ""
	
	icon.button = UILib.buttonIcon(vec3(0.0, 0.0, -10.0), vec2(32.0, 16.0), Config.uiColors.file, 
		vec2(8.0, 8.0), vec2(16.0, 16.0), vec2(0.0, 8.0 * 2), vec2(16.0, 16.0), Config.uiColors.fileIcon, function() Main._assetIconPressed(icon) end)

	icon.button.textureId = TEXTURE_ICONS
	icon.button.texturePos = vec2(0.0, 8.0 * 4)
	icon.button.textureSize = vec2(32.0, 16.0)

	icon.label = UILib.textLine(
		vec3(icon.button.position.x - (((8.0 * 4) - 32.0) / 2), icon.button.position.y + 24.0, -10.0), vec2(8.0 * 4, 8.0), function() Main._assetIconRenamed(icon) end)

	icon.label.color = Config.uiColors.fileIconTextBackground
	icon.label.colorBase = Config.uiColors.fileIconTextBackground
	icon.label.colorHover = Config.uiColors.fileIconTextBackground + 50
	icon.label.textColor = Config.uiColors.fileIconText

	icon.label.cursorSpriteData.color = Config.paletteNames.blue
	icon.label.selectSpriteData.color = Config.paletteNames.blueLight

	icon.label:setFocusWidth(8.0 * 8)
	icon.label:setFocusColor(Config.uiColors.fileIconTextBackground + 50)

	icon.label.startEditCallback = function()
		icon.label.text = icon.name

		icon.label.originalText = icon.name
		
		Main.editingAssetIcon = icon
	end

	icon.label.focusChangeCallback = function(focused)
		if focused == false and icon.label.editing == true then
			_doneEditingAssetIcon(icon)
		end
	end

	icon.label2 = UILib.newSpriteWidget()
	icon.label2.hoverEnabled = true
	icon.label2.color = Config.uiColors.fileIconTextBackground
	icon.label2.colorBase = Config.uiColors.fileIconTextBackground
	icon.label2.colorHover = Config.uiColors.fileIconTextBackground + 50
	icon.label2.textColor = Config.uiColors.fileIconText
	icon.label2.position = vec3(0.0, 0.0, -10.0)
	icon.label2.size = vec2(8 * 4.0, 8.0)
	icon.test = 1
	icon.label2.callback = function()
		icon.test = 2

		icon.label.text = icon.name
		icon.label.changed = true

		icon.label:setEditing(true)
	end

	icon.label.text = ""

	icon.gridPosX = -1
	icon.gridPosY = -1

	return icon
end

function Main._initAssetIcons()
	Main.gridSize = 38.0

	Main.assetIcons = {}
	Main.assetIconsByName = {}

	Main.trashAssetIcons = {}

	-- tell uilib to not collect the next buttons we'll create, we'll update them ourselves
	UILib.setCollectSpritePool(false)

	for i = 1, 32 do
		Main.assetIcons[i] = Main._addAssetIcon()
	end

	for i = 1, 15 do
		Main.trashAssetIcons[i] = Main._addAssetIcon()
		Main.trashAssetIcons[i].trash = true
	end

	UILib.setCollectSpritePool(true)
end

function Main._updateFPS()
	local t = getTime() - Main.lastTimeFPS

	Main.fps = Main.fps + 1

	if t >= 1.0 then
		print(Main.fps)

		Main.lastTimeFPS = getTime()
		Main.fps = 0
	end
end

function Main._makeView(program, viewId, topButton, width, height, fileFormats)
	local view = {}
	Main.views[viewId] = view

	view.id = viewId
	view.enabled = false
	view.size = vec2(width, height)
	view.position = vec3(0.0, 0.0, 0.0)
	view.openFileTypes = fileFormats
	if view.openFileTypes == nil then
		view.openFileTypes = {}
	end
	
	view.topBar = UILib.panel(label, vec3(0.0, 0.0, 0.0), vec2(0.0, 0.0), Config.uiColors.windowBar, Config.uiColors.icon)
	view.topBar.hoverEnabled = true
	view.topBar.textColor = Config.paletteNames.black * 0.5
	view.topBar.textOffset = vec2(0.0, 0.0)
	view.topBar.colorBase = vec4Copy(Config.uiColors.windowBar)
	view.topBar.colorHover = view.topBar.colorBase + 50
	view.topBar.callback = function() Main._topBarClicked(viewId) end
	view.topBar.visible = false

	view.closeButton = UILib.buttonIcon(
		vec3(0.0, 0.0, 0.1), vec2(8.0, 8.0),
		Config.uiColors.windowBar - 20,
		vec2(0.0, 0.0), vec2(8.0, 8.0),
		vec2(8.0 * 5.0, 8.0 * 6.0), vec2(8.0, 8.0),
		Config.uiColors.icon, function() Main._closeCurrentView() end)
	view.closeButton.visible = false

	view.topButton = topButton

	loadProgram(program, view.id)
	
	return view
end

function Main._topBarClicked(viewId)
	local view = Main.views[viewId]

	local topBarPos = view.position
	
	Main.mouseClickOffset = vec2(topBarPos.x, topBarPos.y) - getWindowMousePosition()
	Main.dragginTopBar = viewId
end

function Main._updateDraggingTopBar()
	local view = Main.views[Main.dragginTopBar]

	local topBarPos = view.position

	local mousePos = Main.winMousePos
	local newPos = mousePos + Main.mouseClickOffset

	newPos.x = 4 * round(newPos.x / 4);
	newPos.y = 4 * round(newPos.y / 4);

	if newPos.y < Main.topBarLimitTop then
		newPos.y = Main.topBarLimitTop
	end

	topBarPos.x = newPos.x
	topBarPos.y = newPos.y

	view.position.x = round(newPos.x)
	view.position.y = round(newPos.y)
	
	if Main.mouseButtonReleased == true then
		Main.dragginTopBar = -1
	end

	invokeProgram(view.id, "windowMoved", view.position.x, view.position.y)
end

function Main._closeCurrentView()
	if Main.currentView == nil then
		return
	end

	local view = Main.currentView

	invokeProgram(view.id, "programActive", false)

	view.enabled = false
	view.topBar.visible = false
	view.closeButton.visible = false

	if view.topButton ~= nil then
		UILib.setButtonPressed(view.topButton, false)
	end

	Main.currentView = nil
end

function Main._enableView(viewId)
	local view = Main.views[viewId]

	local winSize = getWindowSize()
	local prevViewPos = vec3(winSize.x - view.size.x - (4 * 22), 8 * 4, 0.0)

	for viewId, view in pairs(Main.views) do
		if view.persistent ~= true then
			if view.enabled == true then
				prevViewPos = view.position

				invokeProgram(view.id, "programActive", false)
			end

			view.enabled = false
			view.topBar.visible = false
			view.closeButton.visible = false

			if view.topButton ~= nil then
				UILib.setButtonPressed(view.topButton, false)
			end
		end
	end

	view.enabled = true
	view.topBar.visible = true
	view.closeButton.visible = true

	view.topBar.size.x = view.size.x
	view.topBar.size.y = 8.0

	view.position.x = prevViewPos.x
	view.position.y = prevViewPos.y

	if view.position.x == 0.0 and view.position.y == 0.0 then
		-- first time init window pos
		view.position.x = (getWindowSize().x * 0.5) - (view.size.x * 0.5)
		view.topBar.position.x = view.position.x
	end

	Main.currentView = view

	if view.position.y < Main.topBarLimitTop then
		view.position.y = Main.topBarLimitTop
	end

	if Main.topButtons[viewId] ~= nil then
		UILib.setButtonPressed(Main.topButtons[viewId], true)
	end

	if Main._modelVisualizerView.enabled == true then
		invokeProgram(MODELVISUALIZER_VIEW, "showLogo")
	end

	invokeProgram(view.id, "programActive", true)
	invokeProgram(view.id, "windowMoved", view.position.x, view.position.y)
end

function Main._updateBackground()
	setDepthEnabled(false)
	
	drawSprite2D(Main.backgroundSprite)
	
	setDepthEnabled(true)
end

function Main._updateAssetIcon(asset)
	if Main.mouseInView ~= MAIN_VIEW then
		Main.mouseButtonPressed = false
	end

	UILib.updateUISprite(asset.button, Main.winMousePos, Main.mouseButtonPressed, Main.mouseButtonDown)

	if asset.button.hovered == true and Main.mouseButtonPressed == true then
		UILib.hoveredSpriteWidget.callback()
	end

	if asset.selected == false then
		asset.button.iconSpriteData.color = Config.uiColors.fileIcon
	else
		asset.button.iconSpriteData.color = Config.uiColors.accent
	end
	
	setSprite(asset.button.iconSprite, asset.button.iconSpriteData)

	UILib.updateUISprite(asset.label, Main.winMousePos, Main.mouseButtonPressed, Main.mouseButtonDown)
	
	if asset.label.editing == false then
		UILib.updateUISprite(asset.label2, Main.winMousePos, Main.mouseButtonPressed, Main.mouseButtonDown)
		
		if asset.label2.hovered == true and Main.mouseButtonPressed == true then
			asset.label2.callback()
		end
	end

	if asset.button.hovered == true or asset.label.hovered == true or asset.label2.hovered == true then
		asset.label.forceHovered = true
		asset.label2.forceHovered = true

		asset.label.changed = true
		asset.label2.changed = true
	else
		asset.label.forceHovered = false
		asset.label2.forceHovered = false

		asset.label.changed = true
		asset.label2.changed = true
	end
end

function Main._updateAssetIcons()
	if Main.draggingAssetIcon ~= nil then
		Main._updateDraggingAssetIcon()
	end

	if Main.trashOpen == true then
		drawSprite2D(Main.trashPanelSprite)

		for i = 1, #Main.trashAssetIcons do
			local asset = Main.trashAssetIcons[i]

			if asset.visible == true then
				Main._updateAssetIcon(asset)
			end
		end
	end

	if Main.draggingAssetIcon ~= nil then
		if Main.draggingAssetIcon.trash == true then
			Main._updateAssetIcon(Main.draggingAssetIcon)
		end
	end
	
	for i = 1, #Main.assetIcons do
		local asset = Main.assetIcons[i]
		
		if asset.visible == true then
			Main._updateAssetIcon(asset)
		end
	end
end

function Main._updateMouse()
	Main.doubleClicked = false

	Main.mouseButtonPressed = getMouseButtonPressed(MOUSE_BUTTON_LEFT)
	Main.mouseButtonDown = getMouseButtonDown(MOUSE_BUTTON_LEFT)
	Main.mouseButtonReleased = getMouseButtonReleased(MOUSE_BUTTON_LEFT)

	if Main.mouseButtonPressed == true then
		if Main.lastClickTime ~= -1.0 then
			local deltaTime = Main.time - Main.lastClickTime
			
			if deltaTime < 0.3 then
				Main.doubleClicked = true

				return
			end
		end

		Main.lastClickTime = Main.time
	end
end

function updateViewShortcuts(keyPressed, viewId)
	local shortcuts = Main.shortcuts[viewId]

	if shortcuts == nil then
		return
	end

	for i = 1, #shortcuts do
		local shortcut = shortcuts[i]

		local allKeysPressed = keyPressed == shortcut.key

		if shortcut.modifier ~= nil then
			if type(shortcut.modifier) == "table" then
				for m = 1, #shortcut.modifier do
					if getKeyDown(shortcut.modifier[m]) == false then
						allKeysPressed = false
					end
				end
			else
				if getKeyDown(shortcut.modifier) == false then
					allKeysPressed = false
				end
				if getKeyDown(KEY_LEFT_SHIFT) or getKeyDown(KEY_LEFT_ALT) then
					allKeysPressed = false
				end
			end
		else
			if getKeyDown(KEY_LEFT_CONTROL) or getKeyDown(KEY_LEFT_SHIFT) or getKeyDown(KEY_LEFT_ALT) then
				allKeysPressed = false
			end
		end

		if allKeysPressed == true then
			invokeProgram(shortcut.programId, shortcut.callback)
		end
	end
end

function updateKeyboardShortcuts()
	Main.shiftKeyDown = getKeyDown(KEY_LEFT_SHIFT)
	Main.ctrlKeyDown = getKeyDown(KEY_LEFT_CONTROL)
	
	local keyPressed = getLastKeyPressed()
	if keyPressed == -1 then
		return
	end
	
	if Main.gameRunning == true then
		if keyPressed == KEY_ESCAPE then
			invokeProgram(GAME_VIEW, "escPressed")

			return
		end
	end

	if Main.editingAssetIcon ~= nil then
		if keyPressed == KEY_ENTER or keyPressed == KEY_RETURN then
			Main.editingAssetIcon.label:setEditing(false)

			_doneEditingAssetIcon(Main.editingAssetIcon)
		end

		if keyPressed == KEY_ESCAPE then
			Main.editingAssetIcon.label.text = Main.editingAssetIcon.label.originalText

			Main.editingAssetIcon.label:setEditing(false)

			_doneEditingAssetIcon(Main.editingAssetIcon)
		end
	end

	updateViewShortcuts(keyPressed, MAIN_VIEW)

	if Main.currentView ~= nil then
		if Main.mouseInView == Main.currentView.id then
			updateViewShortcuts(keyPressed, Main.currentView.id)
		end
	end

	if Main.mouseInView == DEBUGLOG_VIEW then
		updateViewShortcuts(keyPressed, DEBUGLOG_VIEW)
	end
end

function Main._captureGif()
	if Main.currentView == nil then
		Main.capturingGif = false

		return
	end

	invokeProgram(Main.currentView.id, "update")
end

function Main._updateViewsTopBar()
	Main.mouseInView = MAIN_VIEW

	if Main.currentView ~= nil then
		Main.currentView.topBar.position.x = Main.currentView.position.x
		Main.currentView.topBar.position.y = Main.currentView.position.y - 8.0

		Main.currentView.closeButton.position.x = Main.currentView.position.x + Main.currentView.size.x - 8.0
		Main.currentView.closeButton.position.y = Main.currentView.topBar.position.y
		Main.currentView.closeButton.position.z = Main.currentView.topBar.position.z + 0.1
		Main.currentView.closeButton.iconSpriteData.position.x = Main.currentView.closeButton.position.x
		Main.currentView.closeButton.iconSpriteData.position.y = Main.currentView.closeButton.position.y
		Main.currentView.closeButton.iconSpriteData.position.z = Main.currentView.closeButton.position.z + 0.1
		Main.currentView.closeButton.changed = true

		Main.currentView.topBar.changed = true

		local winSize = Main.currentView.topBar.size + Main.currentView.size
		local hovered = intersectRect(Main.winMousePos, vec2(Main.currentView.topBar.position.x, Main.currentView.topBar.position.y), winSize)
		if hovered == true then
			Main.mouseInView = Main.currentView.id
		end
	end

	if Main._modelVisualizerView.enabled == true and Main.gameRunning == false then
		Main._modelVisualizerView.topBar.position.x = Main._modelVisualizerView.position.x
		Main._modelVisualizerView.topBar.position.y = Main._modelVisualizerView.position.y - 8.0

		Main._modelVisualizerView.topBar.size.x = Main._modelVisualizerView.size.x
		Main._modelVisualizerView.topBar.size.y = 8.0

		Main._modelVisualizerView.topBar.changed = true

		Main._modelVisualizerView.closeButton.position.x = Main._modelVisualizerView.position.x + Main._modelVisualizerView.size.x - 8.0
		Main._modelVisualizerView.closeButton.position.y = Main._modelVisualizerView.topBar.position.y
		Main._modelVisualizerView.closeButton.position.z = Main._modelVisualizerView.topBar.position.z + 0.1
		Main._modelVisualizerView.closeButton.iconSpriteData.position.x = Main._modelVisualizerView.closeButton.position.x
		Main._modelVisualizerView.closeButton.iconSpriteData.position.y = Main._modelVisualizerView.closeButton.position.y
		Main._modelVisualizerView.closeButton.iconSpriteData.position.z = Main._modelVisualizerView.closeButton.position.z + 0.1
		Main._modelVisualizerView.closeButton.changed = true

		local winSize = Main._modelVisualizerView.topBar.size + Main._modelVisualizerView.size
		local hovered = intersectRect(Main.winMousePos, vec2(Main._modelVisualizerView.topBar.position.x, Main._modelVisualizerView.topBar.position.y), winSize)
		if hovered == true then
			Main.mouseInView = MODELVISUALIZER_VIEW

			Main._modelVisualizerView.topBar.visible = true
			Main._modelVisualizerView.closeButton.visible = true
		else
			Main._modelVisualizerView.topBar.visible = false
			Main._modelVisualizerView.closeButton.visible = false
		end
	end

	if Main._memoryVisualizerView.enabled == true then
		Main._memoryVisualizerView.topBar.position.x = Main._memoryVisualizerView.position.x
		Main._memoryVisualizerView.topBar.position.y = Main._memoryVisualizerView.position.y - 8.0

		Main._memoryVisualizerView.topBar.size.x = Main._memoryVisualizerView.size.x
		Main._memoryVisualizerView.topBar.size.y = 8.0

		Main._memoryVisualizerView.topBar.changed = true

		Main._memoryVisualizerView.closeButton.position.x = Main._memoryVisualizerView.position.x + Main._memoryVisualizerView.size.x - 8.0
		Main._memoryVisualizerView.closeButton.position.y = Main._memoryVisualizerView.topBar.position.y
		Main._memoryVisualizerView.closeButton.position.z = Main._memoryVisualizerView.topBar.position.z + 0.1
		Main._memoryVisualizerView.closeButton.iconSpriteData.position.x = Main._memoryVisualizerView.closeButton.position.x
		Main._memoryVisualizerView.closeButton.iconSpriteData.position.y = Main._memoryVisualizerView.closeButton.position.y
		Main._memoryVisualizerView.closeButton.iconSpriteData.position.z = Main._memoryVisualizerView.closeButton.position.z + 0.1
		Main._memoryVisualizerView.closeButton.changed = true

		local winSize = Main._memoryVisualizerView.topBar.size + Main._memoryVisualizerView.size
		local hovered = intersectRect(Main.winMousePos, vec2(Main._memoryVisualizerView.topBar.position.x, Main._memoryVisualizerView.topBar.position.y), winSize)
		if hovered == true then
			Main.mouseInView = MEMORYVISUALIZER_VIEW
		end
	end

	if Main._debugLogView.enabled == true then
		Main._debugLogView.topBar.position.x = Main._debugLogView.position.x
		Main._debugLogView.topBar.position.y = Main._debugLogView.position.y - 8.0

		Main._debugLogView.topBar.size.x = Main._debugLogView.size.x
		Main._debugLogView.topBar.size.y = 8.0

		Main._debugLogView.topBar.changed = true

		Main._debugLogView.closeButton.position.x = Main._debugLogView.position.x + Main._debugLogView.size.x - 8.0
		Main._debugLogView.closeButton.position.y = Main._debugLogView.topBar.position.y
		Main._debugLogView.closeButton.position.z = Main._debugLogView.topBar.position.z + 0.1
		Main._debugLogView.closeButton.iconSpriteData.position.x = Main._debugLogView.closeButton.position.x
		Main._debugLogView.closeButton.iconSpriteData.position.y = Main._debugLogView.closeButton.position.y
		Main._debugLogView.closeButton.iconSpriteData.position.z = Main._debugLogView.closeButton.position.z + 0.1
		Main._debugLogView.closeButton.changed = true

		local winSize = Main._debugLogView.topBar.size + Main._debugLogView.size
		local hovered = intersectRect(Main.winMousePos, vec2(Main._debugLogView.topBar.position.x, Main._debugLogView.topBar.position.y), winSize)
		if hovered == true then
			Main.mouseInView = DEBUGLOG_VIEW
		end
	end

	if Main.trashOpen == true or UILib.menuPanel.visible == true then
		Main.mouseInView = MAIN_VIEW
	end
end

function Main._updateDebugLog()
	setViewport(Main._debugLogView.position.x, Main._debugLogView.position.y, Main._debugLogView.size.x, Main._debugLogView.size.y)
	
	invokeProgram(Main._debugLogView.id, "update")
end

function Main._updateModelVisualizer()
	setViewport(Main._modelVisualizerView.position.x, Main._modelVisualizerView.position.y, Main._modelVisualizerView.size.x, Main._modelVisualizerView.size.y)

	invokeProgram(Main._modelVisualizerView.id, "setViewPosition", Main._modelVisualizerView.position.x, Main._modelVisualizerView.position.y)
	invokeProgram(Main._modelVisualizerView.id, "update")
end

function Main._updateMemoryVisualizer()
	setViewport(Main._memoryVisualizerView.position.x, Main._memoryVisualizerView.position.y, Main._memoryVisualizerView.size.x, Main._memoryVisualizerView.size.y)
	
	invokeProgram(Main._memoryVisualizerView.id, "update")
end

function setCursor(cursorId)
	UILib.cursor = cursorId
	UILib._applyCursorType(cursorId)
end

function isGameRunning()
	return Main.gameRunning
end

function setGameRunning(state)
	if Main.gameRunning == state then
		return
	end
	
	Main.gameRunning = state

	if state == true then
		Main._modelVisualizerView.topBar.visible = false
		Main._modelVisualizerView.closeButton.visible = false
	else
		if Main._modelVisualizerView.enabled == true then
			Main._modelVisualizerView.topBar.visible = true
			Main._modelVisualizerView.closeButton.visible = true
		end

		Main.subProgramErrorLogged = false
	end
	
	setPrintWatcherEnabled(state)
end

function Main._updateTooltip()
	if UILib.menuPanel.visible == true or Main.gameRunning == true then
		-- the userPrograms menu will clash with the tooltips, so lets hide them temporarely
		return
	end

	local showTooltip = false

	if Main.tooltipEndTime > Main.time then
		showTooltip = true
	elseif UILib.hoveredSpriteWidget ~= nil and UILib.hoveredSpriteWidget.desc ~= nil then
		local btn = UILib.hoveredSpriteWidget
		local textWidth = 8.0 * #btn.desc
		local posX = math.min(btn.position.x, Main.winSize.x - textWidth)
		Main.tooltipSpriteData.position = vec3(posX, btn.position.y + btn.size.y + 4.0, UILib.uiDepth_overViews + 100)
		Main.tooltipSpriteData.size = vec2(textWidth, 10.0)
		Main.tooltipSpriteData.text = btn.desc
		setSprite(Main.tooltipSprite, Main.tooltipSpriteData)
		showTooltip = true
	end

	if showTooltip then
		drawSprite2D(Main.tooltipSprite)
	end
end

function update()
	if Main.lock == true then
		return
	end

	if Main.capturingGif == true then
		Main._captureGif()

		return
	end

	setInputEnabled(true)

	Main.time = getTime()

	Main.winMousePos = getWindowMousePosition()

	Main._updateMouse()
	
	clearScreen(Config.uiColors.background)
	
	if Main.dragginTopBar ~= -1 then
		Main._updateDraggingTopBar()
	end

	setViewport(0.0, 0.0, Main.winSize.x, Main.winSize.y)
	
	Main._updateBackground()
	
	if Main.gameRunning == false then
		Main._updateViewsTopBar()
	end

	if Main.mouseButtonPressed == true then
		Main.mouseDownStartedInView = Main.currentView ~= nil and Main.mouseInView == Main.currentView.id
	end

	Main.exitFullScreenButton.visible = getWindowFullScreen()
	
	UILib.update() -- update main ui before updating the current view

	updateKeyboardShortcuts()
	
	if Main.currentView ~= nil then
		setViewport(Main.currentView.position.x, Main.currentView.position.y, Main.currentView.size.x, Main.currentView.size.y)
		
		if Main.editingAssetIcon == nil and Main.gameRunning == false then
			-- if not editing an assetIcon label, or selecting debugLog text, or orbiting the camera in modelViewer
			-- then disable keyboard input on main to avoid accidental clicks outside the current view
			if Main.mouseInView ~= Main._debugLogView.id and Main.mouseInView ~= Main._modelVisualizerView.id then
				if Main.mouseButtonDown == true and Main.mouseDownStartedInView == false then
					setInputEnabled(false)
				elseif Main.mouseButtonDown ~= true and Main.mouseInView ~= Main.currentView.id then
					setInputEnabled(false)
				end
			end
		end

		invokeProgram(Main.currentView.id, "update")
	end

	setInputEnabled(true)

	if Main._modelVisualizerView.enabled == true and Main.gameRunning == false then
		Main._updateModelVisualizer()
	end

	if Main._memoryVisualizerView.enabled == true then
		Main._updateMemoryVisualizer()
	end

	if Main._debugLogView.enabled == true then
		Main._updateDebugLog()
	end

	setViewport(0.0, 0.0, Main.winSize.x, Main.winSize.y)

	Main._updateTooltip()

	-- update assetIcons last
	if Main.gameRunning == false then
		Main._updateAssetIcons()
	end

	UILib.drawCursor(Main.winMousePos)
end
