local md = require("debugFunctions")

md.printAlways('Loading fighterm script...')

local action_list = mn.LuaEnums["FightermGroups"]

-- Graphics
local background = gr.loadTexture("terminalbg")
local hBorder = gr.loadTexture("terminalHborder")
local vBorder = gr.loadTexture("terminalVborder")
local header = gr.loadTexture("terminalheader")
local screenWidth = gr.getScreenWidth()
local screenHeight = gr.getScreenHeight()
local left = screenWidth * 0.45 - 100
local top = screenHeight * 0.45 - 100
local width = screenWidth * 0.4
local height = screenHeight * 0.4
local right = left + width
local bottom = top + height
local font = (screenHeight >= 2160) and gr.Fonts['Big'] or gr.Fonts['Main']
-- How much space to reserve at the edges of the box.
local inset = 10
local innerHeight = height - (2 * inset)
local innerWidth = width - (2 * inset)
-- The number of pixels per line.
local lineHeight = font.Height
-- The number of lines that fit in the box.
local maxLines = math.floor(innerHeight / lineHeight)
-- The height/width of a border.
local border = screenHeight * 0.0020

-- A table of what keys are supported by the fighter terminal. To simplify
-- gameplay, we only support a few specific keys. This is provided as a table
-- whose keys are the supported keys and whose values are arbitrary truthy
-- values, so as to provide efficient lookups - Lua doesn't really do "sets".

-- FSO doesn't really handle different keyboard layouts very well. As a
-- workaround, we've chosen a key combination - Alt-Q - that is distinct on
-- QWERTY, AZERTY, and Dvorak keyboards. By listening for all three key
-- combinations, we can guess which mapping the user is actually using, and
-- adjust accordingly.
-- This is a horrible hack and should be removed as soon as FSO supports
-- international keyboards properly.
local keyboards = {
	['QWERTY'] = {
		['A'] = 'A', ['B'] = 'B', ['C'] = 'C', ['D'] = 'D', ['E'] = 'E', ['F'] = 'F',
		['G'] = 'G', ['H'] = 'H', ['I'] = 'I', ['J'] = 'J', ['K'] = 'K', ['L'] = 'L',
		['M'] = 'M', ['N'] = 'N', ['O'] = 'O', ['P'] = 'P', ['Q'] = 'Q', ['R'] = 'R',
		['S'] = 'S', ['T'] = 'T', ['U'] = 'U', ['V'] = 'V', ['W'] = 'W', ['X'] = 'X',
		['Y'] = 'Y', ['Z'] = 'Z', ['1'] = '1', ['2'] = '2', ['3'] = '3', ['4'] = '4',
		['5'] = '5', ['6'] = '6', ['7'] = '7', ['8'] = '8', ['9'] = '9', ['0'] = '0',
		['Spacebar'] = ' ', ['-'] = '-',
		['Backspace'] = 'Backspace', ['Enter'] = 'Enter',
		['Alt-Q'] = 'Quit', ['Up Arrow'] = 'Back', ['Down Arrow'] = 'Next'
	},
	['QWERTZ'] = {
		['A'] = 'A', ['B'] = 'B', ['C'] = 'C', ['D'] = 'D', ['E'] = 'E', ['F'] = 'F',
		['G'] = 'G', ['H'] = 'H', ['I'] = 'I', ['J'] = 'J', ['K'] = 'K', ['L'] = 'L',
		['M'] = 'M', ['N'] = 'N', ['O'] = 'O', ['P'] = 'P', ['Q'] = 'Q', ['R'] = 'R',
		['S'] = 'S', ['T'] = 'T', ['U'] = 'U', ['V'] = 'V', ['W'] = 'W', ['X'] = 'X',
		['Z'] = 'Y', ['Y'] = 'Z', ['1'] = '1', ['2'] = '2', ['3'] = '3', ['4'] = '4',
		['5'] = '5', ['6'] = '6', ['7'] = '7', ['8'] = '8', ['9'] = '9', ['0'] = '0',
		['Spacebar'] = ' ', ['-'] = '-',
		['Backspace'] = 'Backspace', ['Enter'] = 'Enter',
		['Alt-Q'] = 'Quit', ['Up Arrow'] = 'Back', ['Down Arrow'] = 'Next'
	},
	['AZERTY'] = {
		['Q'] = 'A', ['B'] = 'B', ['C'] = 'C', ['D'] = 'D', ['E'] = 'E', ['F'] = 'F',
		['G'] = 'G', ['H'] = 'H', ['I'] = 'I', ['J'] = 'J', ['K'] = 'K', ['L'] = 'L',
		[';'] = 'M', ['N'] = 'N', ['O'] = 'O', ['P'] = 'P', ['A'] = 'Q', ['R'] = 'R',
		['S'] = 'S', ['T'] = 'T', ['U'] = 'U', ['V'] = 'V', ['Z'] = 'W', ['X'] = 'X',
		['Y'] = 'Y', ['W'] = 'Z', ['1'] = '1', ['2'] = '2', ['3'] = '3', ['4'] = '4',
		['5'] = '5', ['6'] = '6', ['7'] = '7', ['8'] = '8', ['9'] = '9', ['0'] = '0',
		['Spacebar'] = ' ', ['-'] = '-',
		['Backspace'] = 'Backspace', ['Enter'] = 'Enter',
		['Alt-A'] = 'Quit', ['Up Arrow'] = 'Back', ['Down Arrow'] = 'Next'
	},
	['Dvorak'] = {
		['A'] = 'A', ['X'] = 'B', ['J'] = 'C', ['E'] = 'D', ['.'] = 'E', ['U'] = 'F',
		['I'] = 'G', ['D'] = 'H', ['C'] = 'I', ['H'] = 'J', ['T'] = 'K', ['N'] = 'L',
		['M'] = 'M', ['B'] = 'N', ['R'] = 'O', ['L'] = 'P', ['\''] = 'Q', ['P'] = 'R',
		['O'] = 'S', ['Y'] = 'T', ['G'] = 'U', ['K'] = 'V', [','] = 'W', ['Q'] = 'X',
		['F'] = 'Y', [';'] = 'Z', ['1'] = '1', ['2'] = '2', ['3'] = '3', ['4'] = '4',
		['5'] = '5', ['6'] = '6', ['7'] = '7', ['8'] = '8', ['9'] = '9', ['0'] = '0',
		['Spacebar'] = ' ', ['['] = '-',
		['Backspace'] = 'Backspace', ['Enter'] = 'Enter',
		['Alt-X'] = 'Quit', ['Up Arrow'] = 'Back', ['Down Arrow'] = 'Next'
	}
}

-- This is used to make a reasonable guess but it's not 100%!
local keyboard_types = {
	['Alt-Q'] = 'QWERTY',
	['Alt-A'] = 'AZERTY',
	['Alt-\''] = 'Dvorak'
}

local Color = {}

-- Colors! Construct a color object with Color.of, then call its :apply() method
-- to set the current drawing color.

function Color.of(r, g, b, a)
	if r or g or b or a then
		if not (r and g and b) then
			ba.error('If any color component is given, then the red, green, and blue components must be given. Only the alpha component is optional.')
		end
		return {
			_r = r,
			_g = g,
			_b = b,
			_a = a or Color.default._a,
			apply = Color.apply
		}
	else
		return Color.default
	end
end

function Color.apply(color)
	gr.setColor(color._r, color._g, color._b, color._a)
end

Color.default = Color.of(205, 205, 205, 200)

-- An "entry" refers to some number of lines on the terminal. An entry has both
-- text and a color. Entries assume they're going to be drawn on the terminal
-- and automatically manage the maximum width of their text.

local Entry = {}

-- Create an entry object. The provided text is taken literally (ie. it should
-- be a string, not an XSTR reference) and is not subject to interpolations.
-- The color is optional and defaults to Color.default.
function Entry.of(text, color)
	return {
		_text = text,
		_color = color or Color.default,
		draw = Entry.draw,
		interpolate = Entry.interpolate,
		getHeight = Entry.getHeight,
		getText = Entry.getText,
		setText = Entry.setText,
	}
end

-- Get an entry from a configuration table. The configuration table should have
-- a field named text, which holds an XSTR reference. It may also have fields
-- named r, g, b, and a, corresponding to the components of a color. If any are
-- given, then all must be given, except that the alpha channel is always
-- optional.
function Entry.fromConfiguration(data)
	local text = data.text
	text = ba.XSTR(text[1], text[2])
	text = ba.replaceTokens(text)
	text = ba.replaceVariables(text)
	local color = Color.of(data.r, data.g, data.b, data.a)
	return Entry.of(text, color)
end

function Entry.draw(entry, y)
	entry._color:apply()
	local x = left + inset
	gr.drawString(entry._text, x, y, x + innerWidth, screenHeight)
end

-- Get the number of lines this entry takes up on the terminal. Multiply this
-- number by lineHeight to get the number of pixels it takes up.
function Entry.getHeight(entry)
	if entry._height then
		-- If we've already calculated this entry's height, we can just reuse that.
		return entry._height
	else
		-- No previous calculation... This is a bit of a hack, but the easiest way
		-- to get the height is with gr.drawString(). Unfortunately, that has the
		-- side-effect of... drawing a string, so we set the drawing color to pure
		-- alpha channel first.
		gr.setColor(0, 0, 0, 0)
		-- Cache the result locally so we don't do this more often than we need to.
		entry._height = gr.drawString(entry._text, 0, 0, innerWidth, screenHeight)
		return entry._height
	end
end

function Entry.getText(entry)
	return entry._text
end

function Entry.setText(entry, text)
	entry._text = text
	-- This entry may take more or fewer lines now, so invalidate the cache.
	entry._height = nil
end

-- Apply text interpolation to this entry. There's currently only one supported
-- interpolation - @PlayerName becomes the name of the current player - but more
-- may be supported in the future. This does not mutate this entry, but returns
-- a new entry with the interpolated text and the same color.
function Entry.interpolate(entry)
	local name = ba.getCurrentPlayer():getName()
	local text = entry._text:gsub('@PlayerName', name)
	return Entry.of(text, entry._color)
end

-- An "input" object is an entry object that represents the input prompt. It
-- provides additional methods to work with the command typed into the input.
-- An input object's text is always the concatenation of some prefix with that
-- command.
local Input = {}

-- Like Entry.of(), except it returns input objects.
function Input.of(text, color)
	local entry = Entry.of(text, color)
	return Input._augment(entry)
end

-- Like Entry.fromConfiguration(), except it returns input objects.
function Input.fromConfiguration(data)
	local entry = Entry.fromConfiguration(data)
	return Input._augment(entry)
end

-- Get the command currently stored in the prompt.
function Input.getCommand(input)
	return input._command
end

-- Add some text (usually a single character) to current command.
function Input.add(input, character)
	input._command = input._command .. character
	Input._update(input, true)
end

-- Delete the last character of the current command, if any. If the current
-- command is empty, this does nothing.
function Input.backspace(input)
	input._command = input._command:sub(1, input._command:len() - 1)
	Input._update(input, true)
end

-- Set the current command to the empty string.
function Input.clear(input)
	input._command = ''
	Input._update(input)
end

function Input.toNormalEntry(input)
	return Entry.of(input._prefix .. ' ' .. input._command, input._color)
end

function Input.interpolate(input)
	return Input._augment(Entry.interpolate(input))
end

function Input.onFrame(input)
	local now = mn.getMissionTime()
	if now > input._nextBlinkTime then
		input._cursorStatus = not input._cursorStatus
		input._nextBlinkTime = now + 1
		Input._update(input)
	end
end

function Input._update(input, keypress)
	if keypress then
		input._cursorStatus = true
		input._nextBlinkTime = mn.getMissionTime() + 1
	end
	local cursor = input._cursorStatus and '_' or ' '
	input:setText(input._prefix .. ' ' .. input._command .. cursor)
end

function Input._augment(entry)
	entry._prefix = entry:getText()
	entry._command = ''
	entry._nextBlinkTime = 0
	entry._cursorStatus = true
	entry.onFrame = Input.onFrame
	entry.add = Input.add
	entry.backspace = Input.backspace
	entry.getCommand = Input.getCommand
	entry.clear = Input.clear
	entry.toNormalEntry = Input.toNormalEntry
	entry.interpolate = Input.interpolate
	return entry
end

local ActionGroup = {}
local actionGroups = {}

function ActionGroup.of(name)
	if name == "<none>" then -- "<none>" is required when adding enums dynamically, so lets just make it an alias of "default"
		name = "default"
	end
	local result = actionGroups[name]
	if result then
		return result
	else
		ba.error('Unknown action group ' .. name)
	end
end

function ActionGroup.initial()
	return actionGroups[mn.getMissionFilename()] or ActionGroup.of('default')
end

function ActionGroup.create(name)
	if actionGroups[name] then
		ba.error('Duplicate action group ' .. name)
	else
		local result = {
			include = {},
			actions = {},
			addIncludes = ActionGroup.addIncludes,
			addAction = ActionGroup.addAction,
			getAction = ActionGroup.getAction,
			handle = ActionGroup.handle,
			start = ActionGroup.start,
		}
		actionGroups[name] = result
		return result
	end
end

function ActionGroup.addIncludes(actionGroup, names)
	if names then
		local include = actionGroup.include
		for _, name in ipairs(names) do
			local target = ActionGroup.of(name)
			include[#include + 1] = target
		end
	end
end

function ActionGroup.addAction(actionGroup, action)
	local actions = actionGroup.actions
	actions[#actions + 1] = action
end

function ActionGroup.getAction(actionGroup, command)
	for _, v in pairs(actionGroup.actions) do
		local match, argument = v:match(command)
		if match then return v, argument end
	end
	for _, v in ipairs(actionGroup.include) do
		local match, argument = v:getAction(command)
		if match then return match, argument end
	end
end

function ActionGroup.handle(actionGroup, command)
	local action, argument = actionGroup:getAction(command)
	if action then
		action:handle(command)
		return argument
	else
		md.warningAlways('unhandled command ' .. command)
	end
end

function ActionGroup.start(actionGroup)
	for _, v in pairs(actionGroup.actions) do
		if v.initial then
			v:handle('')
		end
	end
end

Fighterm = {}

function Fighterm.startActionGroup(group)
	Fighterm._actionGroup = group
	group:start()
end

function Fighterm.reset()
	-- The label at the top of the terminal.
	Fighterm.label = Entry.of('FighTerminal V3.6.43R13')
	-- Is the terminal currently _visible?
	Fighterm._visible = false
	-- Can the user switch on the terminal?
	Fighterm.allowed = false
	-- Is terminal input currently disabled?
	Fighterm.suppressed = false
	Fighterm._keyboard = nil
	Fighterm.input = Input.of('$', Color.of(0, 255, 0, 255))
	-- The text entries to display.
	Fighterm._entries = {}
	-- The history of the player's commands.
	Fighterm._history = {}
	Fighterm._historyIndex = 0
	Fighterm.startActionGroup(ActionGroup.initial())
end

function Fighterm.setKeyboard(kb)
	Fighterm._keyboard = keyboards[kb]
end

local function shouldDrawLinesFromTop()
	local count = #Fighterm._entries
	if count > maxLines then return false end
	local lines = 0
	for i = 1, count do
		lines = lines + Fighterm._entries[i]:getHeight()
		if lines > maxLines then return false end
	end
	if Fighterm.suppressed then
		return true
	else
		lines = lines + Fighterm.input:getHeight()
		return lines <= maxLines
	end
end

local function drawLinesFromTop()
	local y = top + inset
	local count = #Fighterm._entries
	for i = 1, count do
		local entry = Fighterm._entries[i]
		entry:draw(y)
		y = y + (entry:getHeight() * lineHeight)
	end
	if not Fighterm.suppressed then
		Fighterm.input:draw(y)
	end
end

local function drawLinesFromBottom()
	local y = bottom - inset
	local lines = 0
	-- If user input is available, draw the input prompt.
	if not Fighterm.suppressed then
		lines = lines + Fighterm.input:getHeight()
		Fighterm.input:onFrame()
		Fighterm.input:draw(bottom - inset - (lines * lineHeight))
	end
	-- Draw our entries, starting at the bottom and filling up until we're out of space.
	for i = #Fighterm._entries, 1, -1 do
		local entry = Fighterm._entries[i]
		lines = lines + entry:getHeight()
		if lines > maxLines then
			return
		else
			entry:draw(bottom - inset - (lines * lineHeight))
		end
	end
end

function Fighterm.draw()
	if Fighterm._visible then
		local oldFont = gr.CurrentFont
		gr.CurrentFont = font
		-- Draw the box itself.
		gr.drawImage(background, left, top, right, bottom, 0, 0, 1, 1)
		-- Draw the left and right borders. These are taller than the box itself to
		-- line up with the top and bottom borders.
		gr.drawImage(vBorder, left - border, top - 20 - border * 2, left, bottom + border, 0, 0, 1, 1)
		gr.drawImage(vBorder, right, top - 20 - border * 2, right + border, bottom + border, 0, 0, 1, 1)
		-- Draw the bottom border.
		gr.drawImage(hBorder, left, bottom, right, bottom + border, 0, 0, 1, 1)
		-- Draw the top border. There are two of these; the header goes between them.
		gr.drawImage(hBorder, left, top - 20 - border * 2, right, top - 20 - border, 0, 0, 1, 1)
		gr.drawImage(hBorder, left, top - border, right, top, 0, 0, 1, 1)
		-- Draw the header at the top of the box.
		gr.drawImage(header, left, top - 20 - border, right, top - border, 0, 0, 1, 1)
		Fighterm.label:draw(top - 15 - border)
		if shouldDrawLinesFromTop() then
			drawLinesFromTop()
		else
			drawLinesFromBottom()
		end
		gr.CurrentFont = oldFont
	end
end

local function addEntry(entry)
	Fighterm._entries[#Fighterm._entries + 1] = entry
end

local function addHistory(entry)
	local last = #Fighterm._history + 1
	Fighterm._history[last] = entry
	Fighterm._historyIndex = last + 1
end

local function setVisible(visible)
	Fighterm._visible = visible
	local set = visible and "-1" or "0"
	mn.runSEXP("(ignore-key !" .. set .. "! !Spacebar! !Left Ctrl! !Right Ctrl! !Alt-J!)")
	mn.runSEXP("(ignore-key !" .. set .. "! !.! !,! !-! !=! !Shift-/! !/! !\\! !'!)")
	mn.runSEXP("(ignore-key !" .. set .. "! !1! !2! !3! !4! !R! !S! !T! !U! !V! !W! !X! !Y! !Z!)")
	mn.runSEXP("(ignore-key !" .. set .. "! !Tab! ![! !]! !A! !B! !C! !D! !E! !F! !G! !H! !I! !J! !K! !L! !M! !N! !O! !P! !Q!)")
	mn.runSEXP("(ignore-key !" .. set .. "! !Pad 1! !Pad 2! !Pad 3! Pad 4! !Pad 5! !Pad 6! !Pad 7! !Pad 8! !Pad 9! !Pad 0!)")
	mn.runSEXP("(ignore-key !" .. set .. "! !Shift-A! !Shift-B! !Shift-C! !Shift-D! !Shift-E! !Shift-F! !Shift-G! !Shift-H! !Shift-I! !Shift-J! !Shift-K!)")
	mn.runSEXP("(ignore-key !" .. set .. "! !Shift-L! !Shift-M! !Shift-N! !Shift-O! !Shift-P! !Shift-Q! !Shift-R! !Shift-S! !Shift-T! !Shift-U! !Shift-V!)")
	mn.runSEXP("(ignore-key !" .. set .. "! !Shift-W! !Shift-X! !Shift-Y! !Shift-Z! !Shift-1! !Shift-3! !Shift-.! !Shift-,! !Shift-End!)")
	mn.runSEXP("(ignore-key !" .. set .. "! !Alt-H! !Alt-Y! !Alt-T! !Alt-S! !Alt-M! !Backspace! !Pad *! !Pad .! !Pad Enter!)")
	mn.runSEXP("(ignore-key !" .. set .. "! !Pad /! !Pad +! !Pad -! !Insert! !Delete! !Home! !End! !Page Up! !Page Down!)")
	mn.runSEXP("(ignore-key !" .. set .. "! !Alt-D! !Up Arrow! !Down Arrow! !Left Arrow! !Right Arrow! !Scroll Lock! !Shift-Scroll Lock! !Alt-E! !Alt-Shift-E!)")
	mn.runSEXP("(ignore-key !" .. set .. "! !Alt-R! !Alt-X! !shift-Pad +! !Shift-Pad Enter! !Alt-shift-Q! !Alt-A! !Alt-N! !Alt-G!)")
end

local function handleCommand(rawCommand)
end

local execute

execute = function(command)
	local argument = Fighterm._actionGroup:handle(command)
	local sexpvar = mn.SEXPVariables['Fighterm']
	if sexpvar:isValid() then
		sexpvar.Value = command:sub(1, 31)
	end
	if argument then execute(argument) end
end

local function onEnter()
	local input = Fighterm.input
	local command = input:getCommand():gsub('%s$', ''):gsub('^%s', ''):lower()
	if command:len() > 0 then
		addEntry(input:toNormalEntry())
		addHistory(command)
		input:clear()
		execute(command)
	end
end

local function onHistory(delta)
	local index = Fighterm._historyIndex + delta
	local command = Fighterm._history[index]
	if command then
		local input = Fighterm.input
		input:clear()
		input:add(command:upper())
		Fighterm._historyIndex = index
	elseif index > 0 then
		Fighterm._historyIndex = #Fighterm._history + 1
		Fighterm.input:clear()
	end
end

local function onBack()
	onHistory(-1)
end

local function onNext()
	onHistory(1)
end

function Fighterm.onKey(key)
	if Fighterm.allowed and not Fighterm._visible then
		local kb_type = keyboard_types[key]
		if kb_type then
			if ScpuiOptionValues.Keyboard_Type ~= "Default" then
				kb_type = ScpuiOptionValues.Keyboard_Type
			end
			Fighterm.setKeyboard(kb_type)
			setVisible(true)
		end
	elseif Fighterm._visible and Fighterm._keyboard and not Fighterm.suppressed then
		local keyboard = Fighterm._keyboard
		if keyboard[key] == 'Quit' then
			setVisible(false)
		else
			key = string.gsub(key, "Shift%-", "", 1)
			key = string.gsub(key, "Alt%-", "", 1)
			key = string.gsub(key, "Ctrl%-", "", 1)
			key = keyboard[key]
			if key == 'Backspace' then
				Fighterm.input:backspace()
			elseif key == 'Enter' then
				onEnter()
			elseif key == 'Back' then
				onBack()
			elseif key == 'Next' then
				onNext()
			elseif key then
				Fighterm.input:add(key)
			end
		end
	end
end

local function translate(xstrs)
	if xstrs then
		local result = {}
		for i = 1, #xstrs, 2 do
			local j = i + 1
			result[j/2] = ba.XSTR(xstrs[i], xstrs[j])
		end
		return result
	else
		return {}
	end
end

local stepTypes = {}

local function responseStep(config)
	local typeName = tostring(config['type'])
	local factory = stepTypes[typeName]
	if factory then
		return factory(config)
	else
		ba.error('Unknown step type ' .. typeName)
	end
end

function stepTypes.say(data)
	local entry = Entry.fromConfiguration(data)
	return function()
		addEntry(entry:interpolate())
	end
end

function stepTypes.label(data)
	local entry = Entry.fromConfiguration(data)
	return function()
		Fighterm.label = entry:interpolate()
	end
end

function stepTypes.prompt(data)
	local input = Input.fromConfiguration(data)
	return function()
		Fighterm.input = input:interpolate()
	end
end

function stepTypes.set(data)
	local name = data.variable
	local value = data.value
	return function()
		mn.SEXPVariables[name].Value = value
	end
end

function stepTypes.jump(data)
	local group = ActionGroup.of(data.target)
	return function()
		Fighterm.startActionGroup(group)
	end
end

function stepTypes.input(data)
	local suppress = not data.allow
	return function()
		Fighterm.suppressed = suppress
	end
end

function stepTypes.delay(data)
	local seconds = data.seconds or (data.milliseconds / 1000)
	local response = {}
	for i, v in ipairs(data['then']) do
		response[i] = responseStep(v)
	end
	return function()
		mn.waitAsync(seconds):continueWith(function()
			for _, v in ipairs(response) do v() end
		end)
	end
end

function stepTypes.done()
	return function() setVisible(false) end
end

local Action = {}

function Action.of(config)
	local names = translate(config.names)
	local patterns = translate(config.patterns)
	local initial = config.initial
	local response = {}
	for i, v in ipairs(config.response) do
		response[i] = responseStep(v)
	end
	return {
		names = names,
		patterns = patterns,
		response = response,
		initial = initial,
		match = Action.match,
		handle = Action.handle,
	}
end

function Action.match(action, command)
	local c = command:lower()
	for _, v in ipairs(action.names) do
		if c == v:lower() then return true end
	end
	for _, v in ipairs(action.patterns) do
		local _, match, argument = c:find(v)
		if match then return true, argument end
	end
	return false
end

function Action.handle(action, command)
	for _, v in ipairs(action.response) do v(command) end
end

local utils = require('utils')
local config = utils.loadConfig('fighterm.cfg')

-- Add each action item to FRED in alphabetical order
-- A quirk of how this works means capitalized groups are listed before lowercase. Close enough!
local fred_list = {}
for n in pairs(config) do 
	table.insert(fred_list, n)
end
table.sort(fred_list)
for i,n in ipairs(fred_list) do
	action_list:addEnumItem(n)
end


-- First pass: declare all the action groups.
for i, _ in pairs(config) do
	ActionGroup.create(i)
end

-- Second pass: configure them.
for i, v in pairs(config) do
	local group = ActionGroup.of(i)
	group:addIncludes(v.include)
	for j, w in pairs(v.actions) do
		local action = Action.of(w)
		group:addAction(action)
	end
end

mn.LuaSEXPs["allow-terminal"].Action = function()
	Fighterm.allowed = true
end

mn.LuaSEXPs["disallow-terminal"].Action = function()
	Fighterm.allowed = false
end

mn.LuaSEXPs["close-terminal"].Action = function()
	setVisible(false)
end

mn.LuaSEXPs["is-terminal-open"].Action = function()
	return Fighterm._visible
end

mn.LuaSEXPs["add-terminal-text"].Action = function(message, r, g, b, a)
	local color = Color.of(r, g, b, a)
	for line in message:getMessage(true):gmatch("[^\n]+") do
		addEntry(Entry.of(line, color))
	end
end

mn.LuaSEXPs["suppress-terminal"].Action = function()
	Fighterm.suppressed = true
end

mn.LuaSEXPs["unsuppress-terminal"].Action = function()
	Fighterm.suppressed = false
end

mn.LuaSEXPs["start-action-group"].Action = function(name)
	Fighterm.startActionGroup(ActionGroup.of(name))
end
