-- Todo: -Real Zelda graphics
-- -Fade cursor - is there a master opacity setting we can fiddle with?

dofile("lua/utils.lua")
dofile(ProcessSkinFilePath("$/menus.lua"))
-- '$' will be replaced with the path to this skin
LoadGU()

IconPath = ProcessSkinFilePath("$/icons/")
IconExt = ".bmp"
BuiltinAppMenuID = 2

-- Load the font.
Font = GetDefaultBitmapFont()
BitmapFont_EnableShadow(Font, true)
BitmapFont_EnableLineWrap(Font, false)
LineHeight = BitmapFont_GetLineHeight(Font)

ShowLoadingScreen(Font, 15)

DiskFree = GetDiskFreePercent() -- assume this doesn't change during execution

-- Load startup sound.
-- This doesn't use LoadStartupSound() because it only loads MP3s.
if EnableSound then
	StartupSound, e = LoadSoundEffect("$/sounds/start.wav")
	if not StartupSound then ShowError(e) end
end

-- Load the wallpaper, optimize it, and try to fit it in VRAM.
LoadingStep("Loading wallpaper")
BGTexture, e = LoadImage("$/textures/hyrule.bmp", IMG_PAD_NONE)
if not BGTexture then  ShowError(e) end
Image_TrySwizzleToVRAM(BGTexture)


-- Load the textures for each menu.
LoadingStep("Loading games.bmp")
MenuBGTex_Games = LoadImage("$/textures/games.bmp", IMG_PAD_NONE)
LoadingStep("Loading apps.bmp")
MenuBGTex_Apps = LoadImage("$/textures/apps.bmp", IMG_PAD_NONE)
LoadingStep("Loading files.bmp")
MenuBGTex_Files = LoadImage("$/textures/files.bmp", IMG_PAD_NONE)
LoadingStep("Loading notes.bmp")
MenuBGTex_Notes = LoadImage("$/textures/notes.bmp", IMG_PAD_NONE)
LoadingStep("Moving to VRAM")
Image_TrySwizzleToVRAM(MenuBGTex_Games)
Image_TrySwizzleToVRAM(MenuBGTex_Apps)
Image_TrySwizzleToVRAM(MenuBGTex_Files)
Image_TrySwizzleToVRAM(MenuBGTex_Notes)

-- Create buffers to draw each menu's text on.
LoadingStep("Creating buffers")
MenuTextBuf_Games, e = CreateImage(456, 256)
if MenuTextBuf_Games then Image_MoveToVRAM(MenuTextBuf_Games)
else ShowError(e)
end

-- We don't need one for Apps because it's not using text.
MenuTextBuf_Files, e = CreateImage(456, 256)
if MenuTextBuf_Files then Image_MoveToVRAM(MenuTextBuf_Files)
else ShowError(e)
end

MenuTextBuf_Notes, e = CreateImage(456, 256)
if MenuTextBuf_Notes then Image_MoveToVRAM(MenuTextBuf_Notes)
else ShowError(e)
end

-- Load textures for menu titles
LoadingStep("Loading t_*.bmp")
MenuTitleTex_Apps, e = LoadImage("$/textures/t_apps.bmp")
if MenuTitleTex_Apps then Image_TrySwizzleToVRAM(MenuTitleTex_Apps)
else ShowError(e)
end

MenuTitleTex_Files, e = LoadImage("$/textures/t_files.bmp")
if MenuTitleTex_Files then Image_TrySwizzleToVRAM(MenuTitleTex_Files)
else ShowError(e)
end

MenuTitleTex_Games, e = LoadImage("$/textures/t_games.bmp")
if MenuTitleTex_Games then Image_TrySwizzleToVRAM(MenuTitleTex_Games)
else ShowError(e)
end

MenuTitleTex_Notes, e = LoadImage("$/textures/t_notes.bmp")
if MenuTitleTex_Notes then Image_TrySwizzleToVRAM(MenuTitleTex_Notes)
else ShowError(e)
end

-- Load other textures
LoadingStep("Loading other images")
HeartImg, e = LoadImage("$/textures/heart.bmp");
if HeartImg then Image_TrySwizzleToVRAM(HeartImg)
else ShowError(e)
end

EmptyHeartImg, e = LoadImage("$/textures/heart-empty.bmp");
if EmptyHeartImg then Image_TrySwizzleToVRAM(EmptyHeartImg)
else ShowError(e)
end

OutlineHeartImg, e = LoadImage("$/textures/heart-outline.bmp");
if OutlineHeartImg then Image_TrySwizzleToVRAM(OutlineHeartImg)
else ShowError(e)
end

MagicBarImg, e = LoadImage("$/textures/magicbar.bmp");
if MagicBarImg then Image_TrySwizzleToVRAM(MagicBarImg)
else ShowError(e)
end

MagicFillImg, e = LoadImage("$/textures/magicfill.bmp");
if not MagicFillImg then ShowError(e) end --too small to optimize

CursorImg, e = LoadImage("$/textures/cursor.bmp");
if CursorImg then Image_TrySwizzleToVRAM(CursorImg)
else ShowError(e)
end


-- Get installed apps
LoadingStep("Reading app list")
_Apps = GetInstalledPrograms()
NumApps = TableSize(_Apps)
if not _Apps then
	ShowError("Error getting program list!")
	return
elseif NumApps == 0 then
	_Apps = {["No programs found!"] = ""}
end
Apps = NestTable(_Apps, "dir", "name")
SortBySubTable(Apps, "name")


-- Get built-in apps
_BuiltinApps = GetBuiltinApps()
NumBuiltinApps = TableSize(_BuiltinApps)
if not _BuiltinApps then
	ShowError("Error getting built-in program list!")
	return
elseif NumBuiltinApps == 0 then
	_BuiltinApps = {["No programs found!"] = ""}
end
BuiltinApps = NestTable(_BuiltinApps, "id", "name")
SortBySubTable(BuiltinApps, "name")


-- Load icons for built-in apps
LoadingStep("Loading icons")
BuiltinAppIcons = {}
for k, v in pairs(BuiltinApps) do
	Icon = LoadImage(IconPath .. v.id .. IconExt, IMG_PAD_NONE)
	if not Icon then
		Icon, e = LoadImage(IconPath .. "default" .. IconExt, IMG_PAD_NONE)
		if not Icon then
			ShowError("Error loading default icon: " .. e)
			return
		end
	end
	Image_TrySwizzleToVRAM(Icon)
	BuiltinAppIcons[v] = Icon
end

-- Set up wallpaper
LoadingStep("Initializing vertices")
BG = AllocVertices(4)
BGUL = {u=0.0, v=0.0, r=255, g=255, b=255, a=255, x=  0, y=  0, z=0} -- top left
BGLL = {u=0.0, v=272, r=255, g=255, b=255, a=255, x=  0, y=272, z=0} -- bottom left
BGUR = {u=480, v=0.0, r=255, g=255, b=255, a=255, x=480, y=  0, z=0} -- top right
BGLR = {u=480, v=272, r=255, g=255, b=255, a=255, x=480, y=272, z=0} -- bottom right
SetVertices(BG, 0, BGUL, BGLL, BGUR, BGLR)

-- Set up menu title box
TitleBox = AllocVertices(4)
IW, IH, dummy = Image_GetInfo(MenuTitleTex_Apps)
PW, PH = CalcPixelSize()
PW = PW * (IW / 2)
PH = PH * (IH / 2)
TBUL = {u=0.0, v=0.0, r=255, g=255, b=255, a=255, x=-PW, y= PH, z=0.0}
TBLL = {u=0.0, v=1.0, r=255, g=255, b=255, a=255, x=-PW, y=-PH, z=0.0}
TBUR = {u=1.0, v=0.0, r=255, g=255, b=255, a=255, x= PW, y= PH, z=0.0}
TBLR = {u=1.0, v=1.0, r=255, g=255, b=255, a=255, x= PW, y=-PH, z=0.0}
SetVertices(TitleBox, 0, TBUL, TBLL, TBUR, TBLR)


-- Set up icon grid.
IconGridNumCols = 6
IconGridNumRows = 4

Icons = AllocVertices(4)
IW, IH, dummy = Image_GetInfo(Icon) -- Get dimensions of the last icon we loaded
PW, PH = CalcPixelSize()
PW = PW * (IW / 2)
PH = PH * (IH / 2)
IconUL = {u=0.0, v=0.0, r=255, g=255, b=255, a=255, x=-PW, y= PH, z=0.0}
IconLL = {u=0.0, v=1.0, r=255, g=255, b=255, a=255, x=-PW, y=-PH, z=0.0}
IconUR = {u=1.0, v=0.0, r=255, g=255, b=255, a=255, x= PW, y= PH, z=0.0}
IconLR = {u=1.0, v=1.0, r=255, g=255, b=255, a=255, x= PW, y=-PH, z=0.0}
SetVertices(Icons, 0, IconUL, IconLL, IconUR, IconLR)


-- Set up other vertices
HeartVtx = AllocVertices(4)
IW, IH, dummy = Image_GetInfo(HeartImg)
PW, PH = CalcPixelSize()
PW = PW * (IW / 2)
PH = PH * (IH / 2)
HeartUL = {u=0.0, v=0.0, r=255, g=255, b=255, a=255, x=-PW, y= PH, z=0.0}
HeartLL = {u=0.0, v=1.0,  r=255, g=255, b=255, a=255, x=-PW, y=-PH, z=0.0}
HeartUR = {u=1.0,  v=0.0, r=255, g=255, b=255, a=255, x= PW, y= PH, z=0.0}
HeartLR = {u=1.0,  v=1.0,  r=255, g=255, b=255, a=255, x= PW, y=-PH, z=0.0}
SetVertices(HeartVtx, 0, HeartUL, HeartLL, HeartUR, HeartLR)

MagicVtx = AllocVertices(8)
IW, IH, dummy = Image_GetInfo(MagicBarImg)
PW, PH = CalcPixelSize()
MPW, MPH = PW, PH
PW = PW * (IW / 2)
PH = PH * (IH / 2)
MagicUL = {u=0.0, v=0.0, r=255, g=255, b=255, a=255, x=-PW, y= PH, z=0.0}
MagicLL = {u=0.0, v=1.0,  r=255, g=255, b=255, a=255, x=-PW, y=-PH, z=0.0}
MagicUR = {u=1.0,  v=0.0, r=255, g=255, b=255, a=255, x= PW, y= PH, z=0.0}
MagicLR = {u=1.0,  v=1.0,  r=255, g=255, b=255, a=255, x= PW, y=-PH, z=0.0}
SetVertices(MagicVtx, 0, MagicUL, MagicLL, MagicUR, MagicLR)

MagicUL.x = MagicUL.x + (3 * MPW)
MagicLL.x = MagicLL.x + (3 * MPW)
MagicUR.x = MagicUL.x + ((IW - 6) * MPW * DiskFree)
MagicLR.x = MagicLL.x + ((IW - 6) * MPW * DiskFree)

MagicUL.y = MagicUL.y - (2 * MPH)
MagicUR.y = MagicUR.y - (2 * MPH)
MagicLL.y = MagicLL.y + (5 * MPH)
MagicLR.y = MagicLR.y + (5 * MPH)
SetVertices(MagicVtx, 4, MagicUL, MagicLL, MagicUR, MagicLR)


-- Read notes file.
LoadingStep("Reading notes file")
Notes = {}
for line in io.lines("notes.txt") do table.insert(Notes, line) end

-- Set up the menus.
LoadingStep("Initializing menus")
AppBase = "ms0:/PSP/GAME/"
AppExt = "/EBOOT.PBP"
CurDir = "ms0:/"
MenuID = 1
MenuTitle = {MenuTitleTex_Games, MenuTitleTex_Apps, MenuTitleTex_Files, MenuTitleTex_Notes}
MenuTable = {Apps, BuiltinApps, nil, Notes}
MenuNumOpts = {TableSize(Apps), TableSize(BuiltinApps), 0, 0}
MenuDispKey = {"name", "name", nil, nil}
MenuAcceptFunc = {RunThisApp, RunThisBuiltinApp, OpenThisFile, DoNothing} -- run when Accept pressed
MenuCancelFunc = {DoNothing, DoNothing, PrevDir, DoNothing} -- when Cancel pressed
MenuTriangleFunc = {DoNothing, DoNothing, PrevDir, DoNothing}
MenuSquareFunc = {DoNothing, DoNothing, DoNothing, DoNothing}
MenuTextBuf = {MenuTextBuf_Games, nil, MenuTextBuf_Files, MenuTextBuf_Notes}
NumMenus = #MenuNumOpts -- max valid menu ID
Cursor = 0 -- Cursor index
IgnoreCursor = false -- Used to make the cursor move only once each button press
IgnoreButtons = false -- And the accept/cancel/etc buttons
Skip = 0 -- Number of menu items to leave off the top of the menu.
NumLines = 16 -- Number of menu items to display at once.
SelectedItem = 1
GamesMenuID = 1 -- Which menu ID is the Games menu
AppsMenuID = 2
FileMenuID = 3
NotesMenuID = 4
RotCount = 0
RotSpeed = 12
MenuTitleY = 0.46
MenuTX = {0, -(2 * (16/9)), 0, -(2 * (16/9))} -- X translation
MenuTZ = {0, 0, -(2 * (16/9)), -(2 * (16/9))} -- Z translation
MenuRY = {0, 90, -90, 180} -- Y rotation
MenuTTX = {(16/9), -(16/9), (16/9), -(16/9)} -- Title X translation
MenuTTZ = {(16/9) - 1, (16/9) - 1, -((16/9) + 1), -(16/9) - 1} -- Title Z translation
MenuTex = {MenuBGTex_Games, MenuBGTex_Notes, MenuBGTex_Apps, MenuBGTex_Files} -- Texture
MenuTTex = {MenuTitleTex_Games, MenuTitleTex_Notes, MenuTitleTex_Apps, MenuTitleTex_Files} -- Title texture
MenuIdx = {GamesMenuID, NotesMenuID, AppsMenuID, FileMenuID}

if DebugMode then MenuCancelFunc[1] = ExitProgram end

if Buttons.up or Buttons.left or Buttons.down or Buttons.right then
	IgnoreCursor = true
end

Days = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}
Box = CreateBox()

-- Load sound effects.
LoadingStep("Loading sounds")
if EnableSound then
	CursorSound, e = LoadSoundEffect("$/sounds/cursor.wav");
	if not CursorSound then ShowError(e) end

	ActivateSound, e = LoadSoundEffect("$/sounds/select.wav");
	if not ActivateSound then ShowError(e) end

	LSwitchSound, e = LoadSoundEffect("$/sounds/left.wav");
	if not LSwitchSound then ShowError(e) end

	RSwitchSound, e = LoadSoundEffect("$/sounds/right.wav");
	if not RSwitchSound then ShowError(e) end

	BackSound, e = LoadSoundEffect("$/sounds/close.wav");
	if not BackSound then ShowError(e) end
	
	-- Load background music.
	-- '$' will be replaced with the path to this skin
	Music, e = LoadMP3File("$/sounds/zelda.mp3");
	if not Music then
		ShowError(e)
		return
	end
	MP3_Loop(Music, -1) --loop forever
	if not Buttons.triangle then
		MP3_Play(Music)
		MusicPlaying = true
	else MusicPlaying = false
	end
	
	if StartupSound then SoundEffect_Replay(StartupSound) end
end


function FrameBegin()
	Time = GetLocalTime()
	Buttons = GetButtons()
	Battery = GetBattery()
	
	if not MenuTable[FileMenuID] then ReadCurDir() end
	
	-- Handle menu controls
	if RotCount == 0 then
		if MenuID == BuiltinAppMenuID then BuiltinAppMenuControls()
		else DoMenuControls()
		end
	end
	
	-- Set up frame
	sceGuClearDepth(0)
	sceGuClear(GU_DEPTH_BUFFER_BIT)
	ResetMatrices()
	sceGuDisable(GU_BLEND)
	
	-- Draw background
	sceGuDisable(GU_DEPTH_TEST)
	UseTexture(BGTexture)
	RenderVertices(BG, 0, 4, GU_TRIANGLE_STRIP, GU_TRANSFORM_2D)
	
	-- Draw menu
	sceGuDisable(GU_CULL_FACE)
	sceGuDisable(GU_DEPTH_TEST)
	sceGuDisable(GU_STENCIL_TEST)
	sceGuEnable(GU_BLEND)
	sceGuBlendFunc(GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0)
	
	RotateDegrees(0, (90 * (MenuID - 2)) + (90 - RotCount), 0)
	Translate(-(16/9), -1, -(16/9) - 0.0005) -- No, having to add 0.0005 here
	-- doesn't make any sense. I blame the media.
	sceGumPushMatrix()
	
	for Idx = 1,4 do
		--Draw box
		sceGumPopMatrix()
		sceGumPushMatrix()
		RotateDegrees(0, MenuRY[Idx], 0)
		Translate(MenuTX[Idx], 0, MenuTZ[Idx])
		UseTexture(MenuTex[Idx])
		
		for i=0, 47 do
			RenderVertices(Box, i * 4, 4, GU_TRIANGLE_STRIP, GU_TRANSFORM_3D)
		end
		
		--Draw title
		sceGumPopMatrix()
		sceGumPushMatrix()
		RotateDegrees(0, MenuRY[Idx], 0)
		
		-- These numbers are magic. 0.879 is very close to (16/9)/2; the others,
		-- pure hocus-pocus.
		local z = MenuTTZ[Idx] / 2
		if Idx == 4 then z = (MenuTTZ[Idx] / 0.879) end
		
		Translate(MenuTX[Idx] + 0.4, -0.25, z)
		if MenuIdx[Idx] ~= AppsMenuID then DrawMenu(MenuIdx[Idx]) end
		Translate(-(MenuTX[Idx] + 0.4), 0.25, -z)
		
		Translate(MenuTTX[Idx], MenuTitleY, MenuTTZ[Idx])
		UseTexture(MenuTTex[Idx])
		RenderVertices(TitleBox, 0, 4, GU_TRIANGLE_STRIP, GU_TRANSFORM_3D)
	end
	
	sceGumPopMatrix()
	
	-- Do rotation
	if RotCount >= RotSpeed then RotCount = RotCount - RotSpeed
	elseif RotCount <= -RotSpeed then RotCount = RotCount + RotSpeed
	else RotCount = 0
	end
	
	-- Draw menus
	--if MenuID ~= FileMenuID then DrawMenu(GamesMenuID) end
	if MenuID ~= NotesMenuID then DrawBuiltinAppsMenu() end
	--if MenuID ~= GamesMenuID then DrawMenu(FileMenuID) end
	--if MenuID ~= AppsMenuID then DrawMenu(NotesMenuID) end
	
	-- Draw battery life as hearts
	ResetMatrices()
	local NumHearts, d = math.ceil(Battery.percent / 5), -0.0675;
	Translate(-1.325, 0.675, -1);
	sceGumPushMatrix()
	for i=1,20 do
		if i <= NumHearts then
			if Battery.charging then UseTexture(OutlineHeartImg)
			else UseTexture(HeartImg)
			end
		else UseTexture(EmptyHeartImg)
		end
		
		
		if i == 11 then Translate((9 * d), d, 0)
		else Translate(-d, 0, 0)
		end
		RenderVertices(HeartVtx, 0, 4, GU_TRIANGLE_STRIP, GU_TRANSFORM_3D)
	end
	
	-- Draw disk space as magic bar
	sceGumPopMatrix()
	Translate(0.367, d * 2, 0)
	UseTexture(MagicBarImg)
	RenderVertices(MagicVtx, 0, 4, GU_TRIANGLE_STRIP, GU_TRANSFORM_3D)
	UseTexture(MagicFillImg)
	RenderVertices(MagicVtx, 4, 4, GU_TRIANGLE_STRIP, GU_TRANSFORM_3D)
end

function FrameEnd()
	FrameBuf = GetFrameBuf()
	local Speed = GetCPUSpeed()
	
	-- Draw menu
	--[[if MenuID == AppsMenuID and RotCount == 0 then
		BitmapFont_DrawString(FrameBuf, Font, 180, 35, FONT_DRAW_NORMAL, nil,
			BuiltinApps[Cursor + 1].name)
	end]]
	
	-- Draw status
	BitmapFont_DrawString(FrameBuf, Font, 375, 1, FONT_DRAW_NORMAL, nil,
		string.format("%3u%% %u:%02u", Battery.percent, Battery.time / 60,
			Battery.time % 60),
		string.format("%2dC %1.2fV", Battery.temperature, Battery.voltage))
	
	BitmapFont_DrawString(FrameBuf, Font, 20, 262, FONT_DRAW_NORMAL, nil,
		string.format("%s %d/%d/%02d %02d:%02d:%02d", Days[DayOfWeek(Time)],
			Time.year, Time.month, Time.day, Time.hour, Time.minute,
			Time.second))
			
	BitmapFont_DrawString(FrameBuf, Font, 375, 262, FONT_DRAW_NORMAL, nil,
		string.format("%3.0f/%3.0fmhz", Speed.cpu, Speed.bus))
end