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
-- 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 = 12
-- 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
    -- 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
  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
