-- lx-bundle:true

-- This file is part of Lx and Qui, created by Liqube <www.liqube.com>
-- Copyright Liqube Audio. All rights reserved.
-- Lx Interim License. See LICENSE for details.

--- Parse and draw the marked character shared by QUI control captions
-- A single '&' marks the next character, while '&&' displays one literal ampersand
-- @module qui.accelerator

local metrics = require("qui.metrics")
local theme = require("qui.theme")

local accelerator = {}
local default_minimum_width = theme.resolve_metric(metrics.accelerator_underline_min_width)
local default_thickness = theme.resolve_metric(metrics.accelerator_underline_thickness)
local default_inset = theme.resolve_metric(metrics.accelerator_underline_inset)

--- Remove one caption marker and retain the marked character's displayed byte position
-- @param source string caller-supplied caption
-- @param owner string readable owner used in validation errors
-- @return string text caption without marker syntax
-- @return string|nil key lowercase character used by Form keyboard lookup
-- @return integer|nil column one-based byte position in displayed text
function accelerator.parse(source, owner)
	assert(type(source) == "string", owner .. " must be a string")
	local parts = {}
	local output_length = 0
	local key
	local column
	local position = 1

	while position <= #source do
		if source:byte(position) ~= 38 then
			local next_position = utf8.offset(source, 2, position) or #source + 1
			local character = source:sub(position, next_position - 1)
			parts[#parts + 1] = character
			output_length = output_length + #character
			position = next_position
		elseif source:byte(position + 1) == 38 then
			parts[#parts + 1] = "&"
			output_length = output_length + 1
			position = position + 2
		else
			assert(key == nil, owner .. " has more than one accelerator")
			local character_position = position + 1
			assert(character_position <= #source, owner .. " ends with an accelerator marker")
			local next_position = utf8.offset(source, 2, character_position) or #source + 1
			local character = source:sub(character_position, next_position - 1)
			key = character:lower()
			column = output_length + 1
			parts[#parts + 1] = character
			output_length = output_length + #character
			position = next_position
		end
	end

	return table.concat(parts), key, column
end

--- Draw one consistent underline beneath the marked shaped character
-- @param control_canvas table live Control drawing surface
-- @param font table QUI font description
-- @param text string displayed caption
-- @param column integer|nil marked one-based UTF-8 byte position
-- @param text_x number caption left edge
-- @param text_y number caption top edge
-- @param text_height number measured caption height
-- @param color table resolved text color
-- @param options table|nil optional per-Control underline sizes
function accelerator.draw(control_canvas, font, text, column, text_x, text_y, text_height, color, options)
	if column == nil then
		return
	end

	local minimum_width = options and options.accelerator_underline_min_width or default_minimum_width
	local thickness = options and options.accelerator_underline_thickness or default_thickness
	local inset = options and options.accelerator_underline_inset or default_inset
	for _, cluster in ipairs(control_canvas:text_clusters(font, text)) do
		if column >= cluster[1] and column < cluster[2] then
			local cluster_left = math.min(cluster[3], cluster[4])
			local cluster_width = math.abs(cluster[4] - cluster[3])
			local underline_width = math.max(minimum_width, cluster_width)
			local underline_x = text_x + cluster_left + math.floor((cluster_width - underline_width) / 2)
			local underline_y = text_y + text_height - inset

			control_canvas:draw_horizontal_line(
				underline_x,
				underline_y,
				underline_width,
				thickness,
				color
			)
			return
		end
	end
end

return accelerator
