local lib = {}

-- Common UI helper library
local commonui

-- Set of all menus
local MENU_TYPES = { MAIN = 0, EDIT = 1, ADD = 2, BUTTON = 3 }

-- Set of sections within a menu
local MENU_SECTIONS = { HEADER = 0, CONTENT = 1, FOOTER = 2 }

-- Last index of header items (above main content) in menu
local header_height = 0

-- Last index of content items (below header, above footer) in menu
local content_height = 0

-- Stack of menus (see MENU_TYPES)
local menu_stack = { MENU_TYPES.MAIN }

-- Button to select when showing the main menu (so newly added button can be selected)
local initial_button

-- Saved selection on main menu (to restore after configure menu is dismissed)
local main_selection_save

-- Whether configure menu is active (so first item can be selected initially)
local configure_menu_active = false

-- Saved selection on configure menu (to restore after button menu is dismissed)
local configure_selection_save

-- Helper for polling for hotkeys
local hotkey_poller

-- Button being created/edited
local current_button = {}

-- Initial button to select when opening buttons menu
local initial_input

-- Handler for BUTTON menu
local input_menu

-- Returns the section (from MENU_SECTIONS) and the index within that section
local function menu_section(index)
	if index <= header_height then
		return MENU_SECTIONS.HEADER, index
	elseif index <= content_height then
		return MENU_SECTIONS.CONTENT, index - header_height
	else
		return MENU_SECTIONS.FOOTER, index - content_height
	end
end

local function create_new_button()
	return {
		on_frames = 1,
		off_frames = 1,
		counter = 0
	}
end

local function is_button_complete(button)
	return button.port and button.mask and button.type and button.key and button.on_frames and button.off_frames and button.button and button.counter
end

-- Main menu

local function populate_main_menu(buttons)
	local ioport = manager.machine.ioport
	local input = manager.machine.input
	local menu = {}
	table.insert(menu, {_p('plugin-autofire', 'Autofire buttons'), '', 'off'})
	table.insert(menu, {string.format(_p('plugin-autofire', 'Press %s to delete'), manager.ui:get_general_input_setting(ioport:token_to_input_type('UI_CLEAR'))), '', 'off'})
	table.insert(menu, {'---', '', ''})
	header_height = #menu

	-- Use frame rate of first screen or 60Hz if no screens
	local freq = 60
	local screen = manager.machine.screens:at(1)
	if screen then
		freq = 1 / screen.frame_period
	end

	if #buttons > 0 then
		for index, button in ipairs(buttons) do
			-- Round rate to two decimal places
			local rate = freq / (button.on_frames + button.off_frames)
			rate = math.floor(rate * 100) / 100
			local text
			if button.button then
				text = string.format(_p('plugin-autofire', '%s [%g Hz]'), button.button.name, rate)
			else
				text = string.format(_p('plugin-autofire', 'n/a [%g Hz]'), rate)
			end
			table.insert(menu, {text, input:seq_name(button.key), ''})
			if index == initial_button then
				main_selection_save = #menu
			end
		end
	else
		table.insert(menu, {_p('plugin-autofire', '[no autofire buttons]'), '', 'off'})
	end
	initial_button = nil
	content_height = #menu

	table.insert(menu, {'---', '', ''})
	table.insert(menu, {_p('plugin-autofire', 'Add autofire button'), '', ''})

	local selection = main_selection_save
	main_selection_save = nil
	return menu, selection
end

local function handle_main_menu(index, event, buttons)
	local section, adjusted_index = menu_section(index)
	if section == MENU_SECTIONS.CONTENT then
		if event == 'select' then
			main_selection_save = index
			current_button = buttons[adjusted_index]
			table.insert(menu_stack, MENU_TYPES.EDIT)
			return true
		elseif event == 'clear' then
			table.remove(buttons, adjusted_index)
			main_selection_save = index
			if adjusted_index > #buttons then
				main_selection_save = main_selection_save - 1
			end
			return true
		end
	elseif section == MENU_SECTIONS.FOOTER then
		if event == 'select' then
			main_selection_save = index
			current_button = create_new_button()
			table.insert(menu_stack, MENU_TYPES.ADD)
			return true
		end
	end
	return false
end

-- Add/edit menus (mostly identical)

local function populate_configure_menu(menu)
	local button_name
	if current_button.button then
		button_name = current_button.button.name
	elseif current_button.port then
		button_name = _p('plugin-autofire', 'n/a')
	else
		button_name = _p('plugin-autofire', '[not set]')
	end
	local key_name = current_button.key and manager.machine.input:seq_name(current_button.key) or _p('plugin-autofire', '[not set]')
	table.insert(menu, {_p('plugin-autofire', 'Input'), button_name, ''})
	if not (configure_menu_active or configure_selection_save) then
		configure_selection_save = #menu
	end
	table.insert(menu, {_p('plugin-autofire', 'Hotkey'), key_name, hotkey_poller and 'lr' or ''})
	table.insert(menu, {_p('plugin-autofire', 'On frames'), tostring(current_button.on_frames), current_button.on_frames > 1 and 'lr' or 'r'})
	table.insert(menu, {_p('plugin-autofire', 'Off frames'), tostring(current_button.off_frames), current_button.off_frames > 1 and 'lr' or 'r'})
	configure_menu_active = true
end

local function handle_configure_menu(index, event)
	if hotkey_poller then
		-- special handling for polling for hotkey
		if hotkey_poller:poll() then
			if hotkey_poller.sequence then
				current_button.key = hotkey_poller.sequence
				current_button.key_cfg = manager.machine.input:seq_to_tokens(hotkey_poller.sequence)
			end
			hotkey_poller = nil
			return true
		end
		return false
	end

	if index == 1 then
		-- Input
		if event == 'select' then
			configure_selection_save = header_height + index
			table.insert(menu_stack, MENU_TYPES.BUTTON)
			if current_button.port and current_button.button then
				initial_input = current_button.button
			end
			return true
		end
	elseif index == 2 then
		-- Hotkey
		if event == 'select' then
			if not commonui then
				commonui = require('commonui')
			end
			hotkey_poller = commonui.switch_polling_helper()
			return true
		end
	elseif index == 3 then
		-- On frames
		manager.machine:popmessage(_p('plugin-autofire', 'Number of frames button will be pressed'))
		if event == 'left' then
			current_button.on_frames = current_button.on_frames - 1
			return true
		elseif event == 'right' then
			current_button.on_frames = current_button.on_frames + 1
			return true
		elseif event == 'clear' then
			current_button.on_frames = 1
			return true
		end
	elseif index == 4 then
		-- Off frames
		manager.machine:popmessage(_p('plugin-autofire', 'Number of frames button will be released'))
		if event == 'left' then
			current_button.off_frames = current_button.off_frames - 1
			return true
		elseif event == 'right' then
			current_button.off_frames = current_button.off_frames + 1
			return true
		elseif event == 'clear' then
			current_button.off_frames = 1
			return true
		end
	end
	return false
end

local function populate_edit_menu()
	local menu = {}
	table.insert(menu, {_p('plugin-autofire', 'Edit autofire button'), '', 'off'})
	table.insert(menu, {'---', '', ''})
	header_height = #menu

	populate_configure_menu(menu)
	content_height = #menu

	table.insert(menu, {'---', '', ''})
	table.insert(menu, {_p('plugin-autofire', 'Done'), '', ''})

	local selection = configure_selection_save
	configure_selection_save = nil
	if hotkey_poller then
		return hotkey_poller:overlay(menu, selection, 'lrrepeat')
	else
		return menu, selection, 'lrrepeat'
	end
end

local function handle_edit_menu(index, event, buttons)
	local section, adjusted_index = menu_section(index)
	if ((section == MENU_SECTIONS.FOOTER) and (event == 'select')) or (event == 'back') then
		configure_menu_active = false
		table.remove(menu_stack)
		return true
	elseif section == MENU_SECTIONS.CONTENT then
		return handle_configure_menu(adjusted_index, event)
	end
	return false
end

local function populate_add_menu()
	local menu = {}
	table.insert(menu, {_p('plugin-autofire', 'Add autofire button'), '', 'off'})
	table.insert(menu, {'---', '', ''})
	header_height = #menu

	populate_configure_menu(menu)
	content_height = #menu

	table.insert(menu, {'---', '', ''})
	if is_button_complete(current_button) then
		table.insert(menu, {_p('plugin-autofire', 'Create'), '', ''})
	else
		table.insert(menu, {_p('plugin-autofire', 'Cancel'), '', ''})
	end

	local selection = configure_selection_save
	configure_selection_save = nil
	if hotkey_poller then
		return hotkey_poller:overlay(menu, selection, 'lrrepeat')
	else
		return menu, selection, 'lrrepeat'
	end
end

local function handle_add_menu(index, event, buttons)
	local section, adjusted_index = menu_section(index)
	if ((section == MENU_SECTIONS.FOOTER) and (event == 'select')) or (event == 'back') then
		configure_menu_active = false
		table.remove(menu_stack)
		if is_button_complete(current_button) and (event == 'select') then
			table.insert(buttons, current_button)
			initial_button = #buttons
		end
		return true
	elseif section == MENU_SECTIONS.CONTENT then
		return handle_configure_menu(adjusted_index, event)
	end
	return false
end

-- Button selection menu

local function populate_button_menu()
	local function is_supported_input(ioport_field)
		-- IPT_BUTTON1 through IPT_BUTTON16 in ioport_type enum (ioport.h)
		return ioport_field.type >= 64 and ioport_field.type <= 79
	end

	local function action(field)
		if field then
			current_button.port = field.port.tag
			current_button.mask = field.mask
			current_button.type = field.type
			current_button.button = field
		end
		initial_input = nil
		input_menu = nil
		table.remove(menu_stack)
	end

	if not commonui then
		commonui = require('commonui')
	end
	input_menu = commonui.input_selection_menu(action, _p('plugin-autofire', 'Select an input for autofire'), is_supported_input)
	return input_menu:populate(initial_input)
end

local function handle_button_menu(index, event)
	return input_menu:handle(index, event)
end

function lib:init_menu(buttons)
	header_height = 0
	content_height = 0
	menu_stack = { MENU_TYPES.MAIN }
	current_button = {}
	input_menu = nil
end

function lib:populate_menu(buttons)
	local current_menu = menu_stack[#menu_stack]
	if current_menu == MENU_TYPES.MAIN then
		return populate_main_menu(buttons)
	elseif current_menu == MENU_TYPES.EDIT then
		return populate_edit_menu()
	elseif current_menu == MENU_TYPES.ADD then
		return populate_add_menu()
	elseif current_menu == MENU_TYPES.BUTTON then
		return populate_button_menu()
	end
end

function lib:handle_menu_event(index, event, buttons)
	manager.machine:popmessage()
	local current_menu = menu_stack[#menu_stack]
	if current_menu == MENU_TYPES.MAIN then
		return handle_main_menu(index, event, buttons)
	elseif current_menu == MENU_TYPES.EDIT then
		return handle_edit_menu(index, event, buttons)
	elseif current_menu == MENU_TYPES.ADD then
		return handle_add_menu(index, event, buttons)
	elseif current_menu == MENU_TYPES.BUTTON then
		return handle_button_menu(index, event)
	end
end

return lib
