local function distance(x1, y1, x2, y2)
	return math.pow(math.pow(x1 - x2, 2) + math.pow(y1 - y2, 2), 0.5)
end

local children = {}

local Letters = {
	"A",
	"B",
	"C",
	"D",
	"E",
	"F",
	"G",
	"H",
	"I",
	"J",
	"K",
	"L",
	"M",
	"N",
	"O",
	"P",
	"Q",
	"R",
	"S",
	"T",
	"U",
	"V",
	"W",
	"X",
	"Y",
	"Z",
	"0",
	"1",
	"2",
	"3",
	"4",
	"5",
	"6",
	"7",
	"8",
	"9",
	"?",
	"!",
	"BACK",
	"ENTER"
}

local LetterIndexes = {}
for i, l in ipairs(Letters) do
	LetterIndexes[l] = i
end

local MapNameToLetter = {}
local MapLetterToName = {}
local letter =
	LoadFont("Common Normal") ..
	{
		InitCommand = function(self)
			self:zoom(0.8):shadowlength(0)
		end,
		PulseCommand = function(self)
			self:finishtweening()
			local z = self:GetZoomX()
			self:accelerate(0.15):zoom(1.2):decelerate(0.15):zoom(z)
		end
	}

for l in ivalues(Letters) do
	local Name = "letter " .. l
	MapNameToLetter[Name] = l
	MapLetterToName[l] = Name
	children[#children + 1] =
		letter ..
		{
			Name = Name,
			Text = l
		}
	if l == "BACK" or l == "ENTER" then
		children[#children].File = THEME:GetPathF("Common", "Normal")
	end
end
children[#children + 1] = LoadActor(THEME:GetPathS("Common", "value")) .. {Name = "Type", SupportPan = true}
children[#children + 1] = LoadActor(THEME:GetPathS("Common", "cancel")) .. {Name = "Back", SupportPan = true}
children[#children + 1] = LoadActor(THEME:GetPathS("Common", "start")) .. {Name = "Enter", SupportPan = true}
children[#children + 1] = LoadActor(THEME:GetPathS("MusicWheel", "change")) .. {Name = "Move", SupportPan = true}

local CursorFiles = {
	[PLAYER_1] = "Cursor P1",
	[PLAYER_2] = "Cursor P2"
}

for pn in ivalues(PlayerNumber) do
	children[#children + 1] =
		LoadActor(THEME:GetPathG("_frame", "1D"), {4 / 10, 2 / 10, 4 / 10}, LoadActor(CursorFiles[pn])) ..
		{
			Name = CursorFiles[pn],
			BeginCommand = function(self)
				self:visible(SCREENMAN:GetTopScreen():GetEnteringName(pn))
			end,
			OnCommand = function(self)
				self:zoom(0):rotationz(-360 * 2):sleep(0.55):decelerate(0.5):zoom(1):rotationz(0)
			end,
			OffCommand = function(self)
				self:sleep(0.3):queuecommand("TweenOff")
			end,
			PlayerFinishedMessageCommand = function(self, param)
				if param.PlayerNumber ~= pn then
					return
				end
				self:playcommand("TweenOff")
			end,
			TweenOffCommand = function(self, param)
				self:accelerate(0.25):zoomx(0)
			end
		}
end

local c
local Keys = {}
local Selection = {}
local PlayerX = {}
local PlayerY = {}
return Def.ActorFrame {
	children = children,
	BeginCommand = function(self)
		c = self:GetChildren()
		c.Cursors = {}
		c.Cursors[PLAYER_1] = c["Cursor P1"]
		c.Cursors[PLAYER_2] = c["Cursor P2"]

		for l in ivalues(Letters) do
			local Name = MapLetterToName[l]
			assert(Name)
			Keys[l] = {
				Text = c[Name],
				Width = c[Name]:GetWidth(),
				Height = c[Name]:GetHeight()
			}
			assert(c[Name], Name)
		end

		-- Position letters.
		local RowWidths = {}
		local fX = 0
		local fXPadding = 4
		local fY = 0
		local iRow = 1
		for l in ivalues(Letters) do
			if l == "O" or l == "0" or l == "BACK" then
				fX = 0
				fY = fY + 30
				iRow = iRow + 1
			end
			Keys[l].Row = iRow
			Keys[l].Text:x(fX + Keys[l].Width / 2)
			Keys[l].Text:y(fY + Keys[l].Height / 2)
			fX = fX + Keys[l].Width
			RowWidths[iRow] = fX
			fX = fX + fXPadding
		end
		for l in ivalues(Letters) do
			local iRow = Keys[l].Row
			Keys[l].Text:addx(-RowWidths[iRow] / 2)
		end

		Keys["BACK"].Text:x(-140)
		Keys["ENTER"].Text:x(140)

		for pn in ivalues(PlayerNumber) do
			local sName = SCREENMAN:GetTopScreen():GetSelection(pn)
			local DefaultKey = "A"
			if #sName > 0 then
				DefaultKey = "ENTER"
			end

			self:playcommand("SelectKey", {Key = DefaultKey, PlayerNumber = pn})
			c.Cursors[pn]:finishtweening()
		end
	end,
	CodeMessageCommand = function(self, param)
		local pn = param.PlayerNumber
		if not SCREENMAN:GetTopScreen():GetAnyStillEntering() and param.Name == "Enter" then
			if SCREENMAN:GetTopScreen():Finish(pn) then
				c.Enter:play()
			end
			return
		end
		if SCREENMAN:GetTopScreen():GetFinalized(pn) then
			return
		end

		if param.Name == "Left" or param.Name == "Right" then
			local iDir = param.Name == "Left" and -1 or 1

			local idx = LetterIndexes[Selection[pn]]
			idx = idx + iDir
			idx = math.mod(idx + #Letters - 1, #Letters) + 1
			self:playcommand("SelectKey", {Key = Letters[idx], PlayerNumber = pn})
			c.Move:playforplayer(pn)
			return
		end

		if param.Name == "JumpToEnter" then
			self:playcommand("SelectKey", {Key = "ENTER", PlayerNumber = pn})
			c.Move:playforplayer(pn)
			return
		end
		if param.Name == "Backspace" then
			if SCREENMAN:GetTopScreen():Backspace(pn) then
				c.Back:playforplayer(pn)
			end
			return
		end

		if param.Name == "NextRow" or param.Name == "PrevRow" then
			local iDir = param.Name == "PrevRow" and -1 or 1
			local idx = LetterIndexes[Selection[pn]]
			local iRow = Keys[Selection[pn]].Row
			iRow = iRow + iDir
			iRow = math.mod(iRow + 4 - 1, 4) + 1

			local NearestLetter
			local NearestLetterDistance
			for l in ivalues(Letters) do
				if iRow == Keys[l].Row then
					local Text = Keys[l].Text

					local fDist = distance(PlayerX[pn], PlayerY[pn], Text:GetX(), Text:GetY())
					if not NearestLetterDistance or fDist < NearestLetterDistance then
						NearestLetterDistance = fDist
						NearestLetter = l
					end
				end
			end
			assert(NearestLetter)
			self:playcommand("SelectKey", {Key = NearestLetter, PlayerNumber = pn, NoStore = true})

			c.Move:playforplayer(pn)

			return
		end

		if param.Name == "Enter" then
			if Selection[pn] == "BACK" then
				if SCREENMAN:GetTopScreen():Backspace(pn) then
					Keys[Selection[pn]].Text:playcommand("Pulse")
					c.Back:playforplayer(pn)
				end
			elseif Selection[pn] == "ENTER" then
				if SCREENMAN:GetTopScreen():Finish(pn) then
					Keys[Selection[pn]].Text:playcommand("Pulse")
					c.Enter:playforplayer(pn)
				end
			else
				local key = Selection[pn] -- EnterKey may change this
				if SCREENMAN:GetTopScreen():EnterKey(pn, key) then
					Keys[key].Text:playcommand("Pulse")
					c.Type:playforplayer(pn)
				end
			end
		end
	end,
	SelectKeyMessageCommand = function(self, param)
		local key = param.Key
		local pn = param.PlayerNumber
		if not Keys[key] then
			return
		end

		Selection[pn] = key
		c.Cursors[pn]:stoptweening()
		c.Cursors[pn]:playcommand(
			"SetSize",
			{
				Width = Keys[key].Width + 6,
				tween = function(self)
					self:stoptweening():linear(0.10)
				end
			}
		)
		c.Cursors[pn]:x(Keys[key].Text:GetX())
		c.Cursors[pn]:y(Keys[key].Text:GetY())
		if not param.NoStore then
			PlayerX[pn] = Keys[key].Text:GetX()
			PlayerY[pn] = Keys[key].Text:GetY()
		end
	end,
	MenuTimerExpiredMessageCommand = function(self, param)
		for pn in ivalues(PlayerNumber) do
			SCREENMAN:GetTopScreen():Finish(pn)
		end
		c.Enter:play()
	end,
	OnCommand = function(self, param)
		for key in ivalues(Letters) do
			local fDist = distance(Keys[key].Text:GetX(), Keys[key].Text:GetY(), 0, 100)
			local f = function(self)
				self:diffusealpha(0):zoom(0):sleep(0.25 + (fDist / 400)):decelerate(0.5):diffusealpha(1):zoom(0.8)
			end
			f(Keys[key].Text)
		end
	end,
	OffCommand = function(self, param)
		for key in ivalues(Letters) do
			local fDist = distance(Keys[key].Text:GetX(), Keys[key].Text:GetY(), 0, 100)
			local f = function(self)
				self:sleep(0.0 + (fDist / 400)):decelerate(0.5):diffusealpha(0):zoom(0)
			end
			f(Keys[key].Text)
		end
	end
}
