-- This skin demonstrates displaying icons for the built-in programs, in a grid
-- or a ring.

dofile("lua/utils.lua")

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

LoadGU()
ShowLoadingScreen(Font, 9)

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


UseIconRing = true
Use3DIconRing = true
UseClock = true
IconPath = ProcessSkinFilePath("$/icons/")
IconExt = ".bmp"
BuiltinAppMenuID = 2
ClockX, ClockY = 122, 27 -- These are not precise


-- Load startup sound.
LoadingStep("Loading startup sound")
LoadStartupSound()


-- Load the wallpaper, optimize it, and try to fit it in VRAM.
LoadingStep("Loading wallpaper")
BGTexture, e = LoadImage(WallpaperPath, IMG_PAD_NONE)
if not BGTexture then 
	ShowError(e)
	return
end
LoadingStep("Moving to VRAM")
Image_TrySwizzleToVRAM(BGTexture)


-- 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
	if not Image_SwizzleToVRAM(Icon) then Image_Swizzle(Icon) end
	BuiltinAppIcons[v] = Icon
end


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 icon grid.
if Use3DIconRing then IconGridNumCols = math.floor(NumBuiltinApps / 4)
else IconGridNumCols = 6
end
IconRotation = 0
IconRingRotation = 0

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 clock
if UseClock then
	Clock = AllocVertices(6)
	ClockH1 = {u=0.0, v=0.0, r=255, g=255, b=255, a=255, x=0.0, y=0.0,  z=0.0}
	ClockH2 = {u=0.0, v=1.0, r=255, g=  0, b=  0, a=255, x=0.0, y=0.65, z=0.0}
	ClockM1 = {u=0.0, v=0.0, r=255, g=255, b=255, a=255, x=0.0, y=0.0,  z=0.0}
	ClockM2 = {u=0.0, v=1.0, r=  0, g=255, b=  0, a=255, x=0.0, y=0.85, z=0.0}
	ClockS1 = {u=0.0, v=0.0, r=255, g=255, b=255, a=255, x=0.0, y=0.0,  z=0.0}
	ClockS2 = {u=0.0, v=1.0, r=  0, g=  0, b=255, a=255, x=0.0, y=1.0,  z=0.0}
	SetVertices(Clock, 0, ClockH1, ClockH2, ClockM1, ClockM2, ClockS1, ClockS2)
end


-- 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 = {"Select Program", "Select Program", "Select File", "Notes"}
MenuX = {160, 160, 160, 160} -- You might want to move the file menu over if you have long file names; 8 is good
MenuY = {16, 16, 16, 16}
MenuTable = {Apps, BuiltinApps, Files, 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}
NumMenus = #MenuNumOpts -- max valid menu ID
MenuYMax = 260
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 = 22 -- Number of menu items to display at once.
SelectedItem = 1
FileMenuID = 3 -- Which menu ID is the Select File menu

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"}

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

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

	SwitchSound, e = LoadSoundEffect("$/switch.wav");
	if not SwitchSound then
		ShowError(e)
		return
	end

	BackSound, e = LoadSoundEffect("$/back.wav");
	if not BackSound then
		ShowError(e)
		return
	end

	PlayStartupSound()
end


function DrawClock()
	local s = (Time.second / 60) * -360
	local m = (Time.minute / 60) * -360
	local h = (Time.hour / 12) * -360
	local scale = 35.2

	sceGuDisable(GU_TEXTURE_2D)
	Translate((240 - ClockX) / -scale, (136 - ClockY) / scale, -5)
	sceGumPushMatrix()
	RotateDegrees(0, 0, s)
	RenderVertices(Clock, 4, 2, GU_LINES, GU_TRANSFORM_3D)
	
	sceGumPopMatrix()
	sceGumPushMatrix()
	RotateDegrees(0, 0, m)
	RenderVertices(Clock, 2, 2, GU_LINES, GU_TRANSFORM_3D)
	
	sceGumPopMatrix()
	sceGumPushMatrix()
	RotateDegrees(0, 0, h)
	RenderVertices(Clock, 0, 2, GU_LINES, GU_TRANSFORM_3D)
	sceGumPopMatrix()
end


function FrameBegin()
	Time = GetLocalTime()
	Buttons = GetButtons()
	
	-- Debug function to find the best Z position for your icon grid, as it
	-- varies depending on the icon size and hell if I know how to calculate it.
	--[[if Buttons.triangle then IconGridZPos = IconGridZPos + 0.001
	elseif Buttons.square then IconGridZPos = IconGridZPos - 0.001
	end]]
	
	-- Handle menu controls
	if MenuID == BuiltinAppMenuID then BuiltinAppMenuControls()
	else DoMenuControls()
	end
	
	-- Set up frame
	sceGuClearDepth(0)
	sceGuClear(GU_DEPTH_BUFFER_BIT)
	ResetMatrices()
	
	-- Draw background
	sceGuDisable(GU_DEPTH_TEST)
	UseTexture(BGTexture)
	RenderVertices(BG, 0, 4, GU_TRIANGLE_STRIP, GU_TRANSFORM_2D)
	sceGuEnable(GU_DEPTH_TEST)
	sceGuDisable(GU_CULL_FACE)
	
	if UseClock then DrawClock() end
	
	-- Draw icon grid
	if MenuID == BuiltinAppMenuID then DrawBuiltinAppsMenu() end
end

function FrameEnd()
	FrameBuf = GetFrameBuf()
	local Battery = GetBattery()
	local Speed = GetCPUSpeed()
	
	-- Draw menu
	if MenuID ~= BuiltinAppMenuID then
		DrawMenu(MenuX[MenuID], MenuY[MenuID], MenuTable[MenuID], MenuDispKey[MenuID])
	else
		BitmapFont_DrawString(FrameBuf, Font, 160, 14, FONT_DRAW_NORMAL, nil,
			BuiltinApps[Cursor + 1].name)
	end
	BitmapFont_DrawString(FrameBuf, Font, 160, 2, FONT_DRAW_NORMAL, nil,
		MenuTitle[MenuID] --[[.. " " .. tostring(IconGridZPos)]])
	
	-- Draw status
	BitmapFont_DrawString(FrameBuf, Font, 410, 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, 0, 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, 395, 262, FONT_DRAW_NORMAL, nil,
		string.format("%3.0f/%3.0fmhz", Speed.cpu, Speed.bus))
		
	-- Rotate icons
	if MenuID == BuiltinAppMenuID then
		IconRotation = IconRotation + 0.05
		
		if IconRingRotation > -0.1 and IconRingRotation < 0.1 then
			IconRingRotation = 0
		elseif IconRingRotation > -0.1 then IconRingRotation = IconRingRotation - 0.1
		elseif IconRingRotation < 0.1 then IconRingRotation = IconRingRotation + 0.1
		end
	end
end