-- Some functions that really should be built in

-- Counts elements in a table.
-- Inputs: Table.
-- Returns: # elements.
function TableSize(t)
	local n = 0
	if not t then return 0 end
	for k in pairs(t) do n = n + 1 end
	return n
end

-- Swaps keys and values in a table.
-- Inputs: Table.
-- Returns: Inverted table.
function InvertTable(t)
	n = {}
	for k, v in pairs(t) do
		n[v] = k
	end
	return n
end

-- Counts digits in a number.
-- Inputs: Number.
-- Returns: # digits.
function NumDigits(n)
	d = 1
	while n >= 10 do
		n = n / 10
		d = d + 1
	end
	return d
end

-- Rounds to specified number of decimal places. (Copied from lua-users.org.)
-- Inputs: Number, # decimal places.
-- Returns: Rounded number.
function round(n, d)
	local m = 10 ^ (d or 0)
	return math.floor(n * m + 0.5) / m
end

-- Determines next power of two.
-- Inputs: Number.
-- Returns: Next power of two.
function NextPowerOfTwo(n)
	p = 1
	while p < n do p = p * 2 end
	return p
end

-- Determines day of week of a date.
-- Inputs: Time (e.g. from GetTime())
-- Returns: Day of week, from 1 (Sunday) to 7 (Saturday).
function DayOfWeek(Time)
	m = Time.month - 2
	if m < 1 then m = m + 12 end
	y = Time.year % 100
	c = math.floor(Time.year / 100)
	w = (Time.day + math.floor((2.6 * m) - 0.2) + y + math.floor(y / 4) +
		math.floor(c / 4) - (2 * c)) % 7
	if w == 0 then w = 7 end
	return w
end

-- Finds last instance of a character in a string.
-- Inputs: String, character.
-- Returns: Position of character, nil if not found.
function FindLast(Str, Chr)
	S = string.reverse(Str)
	P = string.find(S, Chr, 1, true)
	if not P then return nil end
	return (#Str - P) + 1
end

-- Converts HSV to RGB. (Copied from Wikipedia.)
-- Inputs: h, s, v (h in range 0-360, s and v in range 0-1)
-- Returns: r, g, b (in range 0-255)
function HSVToRGB(h, s, v)
	local hi = math.floor((h / 60) % 6)
	local f = (h / 60) - math.floor(h / 60)
	local p = v * (1 - s)
	local q = v * (1 - f * s)
	local t = v * (1 - (1 - f) * s)
	
	if hi == 0 then return v * 255, t * 255, p * 255
	elseif hi == 1 then return q * 255, v * 255, p * 255
	elseif hi == 2 then return p * 255, v * 255, t * 255
	elseif hi == 3 then return p * 255, q * 255, v * 255
	elseif hi == 4 then return t * 255, p * 255, v * 255
	else return v * 255, p * 255, q * 255
	end
end

-- No-op, useful for function tables and goggle implementations.
function DoNothing() end

-- Other useful things

-- Splits a table into two tables with integer keys, one containing the keys and
-- one containing the values.
-- Inputs: Table.
-- Returns: Table of keys and table of values.
function SplitTable(t)
	keys = {}
	values = {}
	for k, v in pairs(t) do
		table.insert(keys, k)
		table.insert(values, v)
	end
	return keys, values
end

-- Creates a table with integer keys in which each value is a table containing
-- the key and value of another table.
-- Inputs:
-- -t: Table.
-- -key: Key for the subtable keys.
-- -val: Key for the subtable values.
-- e.g. NestTable({foo="bar", omg="hats"}, "k", "v") will produce this table:
-- 1 => {k => foo, v => bar}
-- 2 => {k => omg, v => hats}
function NestTable(t, key, val)
	nt = {}
	for k, v in pairs(t) do
		table.insert(nt, {[key]=k, [val]=v})
	end
	return nt
end

-- Sorts a table in which the keys are tables.
-- Inputs:
-- t: Table to sort.
-- k: Key of the subtables to sort by.
function SortBySubTable(t, k)
	table.sort(t, function(a, b)
		return string.lower(a[k]) < string.lower(b[k]) end)
end

-- Plays a sound effect from the beginning.
function SoundEffect_Replay(Sound)
	SoundEffect_Restart(Sound)
	SoundEffect_Play(Sound)
end

-- Swizzles an image, moving to VRAM if possible.
-- Inputs: Image.
-- Returns: true if successfully moved to VRAM, false if not. (The image will
-- be swizzled in either case.)
function Image_TrySwizzleToVRAM(Img)
	if not Image_SwizzleToVRAM(Img) then
		Image_Swizzle(Img)
		return false
	end
	return true
end

-- Calculates the size of a pixel one unit from the camera, using the default
-- perspective.
-- Returns: Width and Height. A polygon drawn at size (x*Width, y*Height) one
-- unit in front of the camera will occupy (x,y) pixels on the screen.
-- Written by phlip at xkcd.com.
function CalcPixelSize()
	local aspect = 480 / 272
	local fov_rad = 75 * (math.pi / 180) -- FOV in radians
	local vpitch = math.tan(fov_rad / 2) * 2 -- FOV height at distance 1
	local hpitch = vpitch * aspect
	return (hpitch / 480), (vpitch / 272)
end

-- Displays a loading screen.
-- Inputs:
-- -Font: Font to render in.
-- -[NumSteps]: If specified, the number of steps (i.e. the number of times
--  LoadingStep() will be called); used to draw an empty progress bar.
-- Notes:
-- -This will automatically be cleared when the first frame is drawn.
-- -You need to call LoadGU() before using this, as it contains
--  GetPrevFrameBuf(). (todo: it probably shouldn't, because we might not need
--  all that if we just want 2D graphics.)
function ShowLoadingScreen(Font, NumSteps)
	LoadStep = 0
	LoadingFont = Font
	LoadingFontHeight = BitmapFont_GetLineHeight(Font)
	FrameBuf = GetPrevFrameBuf() -- we need previous frame because the buffers
	-- won't be swapped until loading has finished; thus "previous" is actually
	-- the current frame buffer
	if not DebugMode then
		BitmapFont_DrawString(FrameBuf, LoadingFont, 200, 131, FONT_DRAW_NORMAL,
			nil, "Loading...")
		if NumSteps then
			for i=0,NumSteps - 1 do
				BitmapFont_DrawString(FrameBuf, LoadingFont, (i * 2) + 200, 135,
				FONT_DRAW_NORMAL, 127, 127, 127, 255, ".")
			end
		end
	end
end

-- Updates the loading screen to show another step has completed.
-- In debug mode, str is displayed on the screen and output to stdout.
-- Otherwise, it is ignored, and for each call another period is drawn below the
-- "Loading" text. The periods are spaced zero pixels apart so as to form a
-- solid bar.
function LoadingStep(str)
	if DebugMode then
		print(str)
		BitmapFont_DrawString(FrameBuf, LoadingFont, 0,
			LoadStep * LoadingFontHeight, FONT_DRAW_NORMAL, nil, str)
	else
		-- We'll assume a period is 2 pixels because there's not yet a function
		-- to get character widths.
		BitmapFont_DrawString(FrameBuf, LoadingFont, (LoadStep * 2) + 200,
			135, FONT_DRAW_NORMAL, nil, ".")
	end
	LoadStep = LoadStep + 1
end

-- Loads the startup sound, if sound is enabled.
-- Inputs:
-- -[Path]: If set, path to the sound to play. Defaults to StartupSoundPath set
--  in boot.lua. Must be a compatible MP3 file.
-- Returns: MP3 object on success, nil on failure.
function LoadStartupSound(Path)
	if not EnableSound then return nil end
	if not Path then Path = StartupSoundPath end
	StartupSound, e = LoadMP3File(Path)
	if StartupSound then
		MP3_Loop(StartupSound, 0) -- no looping
		return StartupSound
	end
	return nil
end

-- Plays the startup sound, if sound is enabled.
function PlayStartupSound()
	if StartupSound then MP3_Play(StartupSound) end
end

-- Useful constants
IMG_PAD_NONE = 0
IMG_PAD_WIDTH = 1
IMG_PAD_HEIGHT = 2
IMG_PAD_BOTH = 3
