-- This is a player context script for MOO2 1.50 patch project.
--
-- This script shows you some general information about the game.
-- This includes certain starting settings and mods of the currently loaded savestate.
--
-- To run the script press 0 in main screen (the file should be named MAIN0.LUA)

-- if the left term has to be shortened to fit, then use the right term instead

CUSTOM_ABBREVIATIONS = {
	['example term that is too long'] = 'shorter version'
}

ALIGN_LEFT = 'left'
ALIGN_MIDDLE = 'middle'
ALIGN_RIGHT = 'right'
PLAYER_COLUMN_ALIGNMENT = ALIGN_MIDDLE

ABBREVIATION_CHARACTER = '.'

--[[ NOTES: 
font index (FONT.LBX) is 3, distance between two characters is 2 pixels
@ is displayed as ~
^ is displayed as •
| is displayed empty 1 pixel wide (producing a 3 pixel gap between characters)
~ is displayed is displayed as 0 pixel wide (producing a 2 pixel gap between characters)
--]]

CHARACTER_TO_PIXEL_WIDTH = {
	[' '] = 3, ['!'] = 2, ['"'] = 6, ['#'] = 7, ['$'] = 6, ['%'] = 11,['&'] = 7, ["'"] = 4, ['('] = 3, [')'] = 3, ['*'] = 13,
	['+'] = 7, [','] = 3, ['-'] = 4, ['.'] = 2, ['/'] = 4, ['0'] = 6, ['1'] = 4, ['2'] = 6, ['3'] = 6, ['4'] = 7,
	['5'] = 6, ['6'] = 6, ['7'] = 6, ['8'] = 6, ['9'] = 6, [':'] = 2, [';'] = 3, ['<'] = 6, ['='] = 6, ['>'] = 6,
	['?'] = 6, ['@'] = 10,['A'] = 8, ['B'] = 8, ['C'] = 7, ['D'] = 8, ['E'] = 7, ['F'] = 7, ['G'] = 8, ['H'] = 8,
	['I'] = 2, ['J'] = 6, ['K'] = 8, ['L'] = 7, ['M'] = 10,['N'] = 8, ['O'] = 8, ['P'] = 8, ['Q'] = 8, ['R'] = 9,
	['S'] = 8, ['T'] = 8, ['U'] = 8, ['V'] = 8, ['W'] = 14,['X'] = 9, ['Y'] = 10,['Z'] = 9, ['['] = 3, ['\\'] = 4,
	[']'] = 3, ['^'] = 9, ['_'] = 8, ['`'] = 3, ['a'] = 6, ['b'] = 6, ['c'] = 6, ['d'] = 6, ['e'] = 6, ['f'] = 4,
	['g'] = 6, ['h'] = 6, ['i'] = 2, ['j'] = 3, ['k'] = 6, ['l'] = 2, ['m'] = 10,['n'] = 6, ['o'] = 6, ['p'] = 6,
	['q'] = 6, ['r'] = 5, ['s'] = 6, ['t'] = 4, ['u'] = 6, ['v'] = 8, ['w'] = 12,['x'] = 8, ['y'] = 8, ['z'] = 6,
	['{'] = 4, ['|'] = 1, ['}'] = 4, ['~'] = 0,
}
DISTANCE_BETWEEN_CHARACTERS = 2
MAXIMUM_WIDTH_FOR_MSGBOX = 339
ROW_SEPARATOR_CHARACTER = ' ' -- only a single character is supported yet, need to adjust ROW_SEPARATOR_WIDTH otherwise
ROW_SEPARATOR_WIDTH = DISTANCE_BETWEEN_CHARACTERS + CHARACTER_TO_PIXEL_WIDTH[ROW_SEPARATOR_CHARACTER] + DISTANCE_BETWEEN_CHARACTERS
ADD_TWO_PIXELS_CHARACTER = '~'
ADD_THREE_PIXELS_CHARACTER = '|'
ADD_FOUR_PIXELS_CHARACTER = '~~'
ADD_FIVE_PIXELS_CHARACTER = ' '
ABBREVIATION_CHARACTER_WIDTH = CHARACTER_TO_PIXEL_WIDTH[ABBREVIATION_CHARACTER]

function get_pixel_width_of_string(input)
	local str = tostring(input)
	if str == nil then
		return 0
	end
	local width = 0
	local gain = 0
    for i = 1, #str do
        gain = CHARACTER_TO_PIXEL_WIDTH[str:sub(i, i)]
		if gain ~= nil then
			width = width + gain + DISTANCE_BETWEEN_CHARACTERS
		end
    end
	if #str > 0 then -- first character produces no spacing-between-characters
		width = width - DISTANCE_BETWEEN_CHARACTERS
	end
	return width
end

function trim(str)
	for i = #str, 0, -1 do
		if str:sub(i, i) ~= ' ' then
			return str:sub(0, i)
		end
	end
end

function shorten_without_aligment(input, width_to_lose, desired_pixel_width)
	local str = tostring(input)
	if #str == 0 then
		return str
	end
    for i = #str, 0, -1 do
        loss = CHARACTER_TO_PIXEL_WIDTH[str:sub(i, i)]
		if loss ~= nil then
			width_to_lose = width_to_lose - loss - DISTANCE_BETWEEN_CHARACTERS
		end
		if width_to_lose + DISTANCE_BETWEEN_CHARACTERS + ABBREVIATION_CHARACTER_WIDTH <= 0 then
			str = str:sub(0 ,i - 1)
			break
		end
    end
	return trim(str) .. ABBREVIATION_CHARACTER
end

function fill_up_string_left_aligned(str, missing_width, desired_pixel_width)
	if missing_width == 2 then
		str = str .. ADD_TWO_PIXELS_CHARACTER
	elseif missing_width == 3 then
		str = str .. ADD_THREE_PIXELS_CHARACTER
	elseif missing_width == 4 then
		str = str .. ADD_FOUR_PIXELS_CHARACTER
	elseif missing_width >= 5 then
		local divisible_by_5_remainder = missing_width % 5
		if divisible_by_5_remainder == 4 then -- 9->5, 14->10, ...
			str = str .. ADD_FOUR_PIXELS_CHARACTER
			missing_width = missing_width - 4
		elseif divisible_by_5_remainder == 3 then -- 8->5, 13->10, ...
			str = str .. ADD_THREE_PIXELS_CHARACTER
			missing_width = missing_width - 3
		elseif divisible_by_5_remainder == 2 then -- 7->5, 12->10, ...
			str = str .. ADD_TWO_PIXELS_CHARACTER
			missing_width = missing_width - 2
		elseif divisible_by_5_remainder == 1 then -- 6->0, 11->5, ...
			str = str .. ADD_THREE_PIXELS_CHARACTER .. ADD_THREE_PIXELS_CHARACTER
			missing_width = missing_width - 6
		end
		if (desired_pixel_width - get_pixel_width_of_string(str)) % 5 ~= 0 then
			msgbox('unexpected remainder for str ' .. tostring(str) .. ': ' .. tostring(desired_pixel_width - get_pixel_width_of_string(str)) % 5) -- TODO: remove this after testing
		end
		str = str .. string.rep(ADD_FIVE_PIXELS_CHARACTER, (missing_width) / 5)
	end
	return str
end

function fill_up_string_right_aligned(str, missing_width, desired_pixel_width)
	if missing_width == 2 then
		str = ADD_TWO_PIXELS_CHARACTER .. str
	elseif missing_width == 3 then
		str = ADD_THREE_PIXELS_CHARACTER .. str
	elseif missing_width == 4 then
		str = ADD_FOUR_PIXELS_CHARACTER .. str
	elseif missing_width >= 5 then
		local divisible_by_5_remainder = missing_width % 5
		if divisible_by_5_remainder == 4 then -- 9->5, 14->10, ...
			str = ADD_FOUR_PIXELS_CHARACTER .. str
			missing_width = missing_width - 4
		elseif divisible_by_5_remainder == 3 then -- 8->5, 13->10, ...
			str = ADD_THREE_PIXELS_CHARACTER .. str
			missing_width = missing_width - 3
		elseif divisible_by_5_remainder == 2 then -- 7->5, 12->10, ...
			str = ADD_TWO_PIXELS_CHARACTER .. str
			missing_width = missing_width - 2
		elseif divisible_by_5_remainder == 1 then -- 6->0, 11->5, ...
			str = ADD_THREE_PIXELS_CHARACTER .. ADD_THREE_PIXELS_CHARACTER .. str
			missing_width = missing_width - 6
		end
		if (desired_pixel_width - get_pixel_width_of_string(str)) % 5 ~= 0 then
			msgbox('unexpected remainder for str ' .. tostring(str) .. ': ' .. tostring(desired_pixel_width - get_pixel_width_of_string(str)) % 5)
		end
		str = string.rep(ADD_FIVE_PIXELS_CHARACTER, (missing_width) / 5) .. str
	end
	return str
end

function fill_up_string_middle_aligned(str, missing_width, desired_pixel_width)
	if missing_width == 2 then
		str = ADD_TWO_PIXELS_CHARACTER .. str
	elseif missing_width == 3 then
		str = ADD_THREE_PIXELS_CHARACTER .. str	
	elseif missing_width == 4 then
		str = ADD_TWO_PIXELS_CHARACTER .. str .. ADD_TWO_PIXELS_CHARACTER
	elseif missing_width == 5 then
		str = ADD_THREE_PIXELS_CHARACTER .. str .. ADD_TWO_PIXELS_CHARACTER
	elseif missing_width == 6 then
		str = ADD_THREE_PIXELS_CHARACTER .. str .. ADD_THREE_PIXELS_CHARACTER
	elseif missing_width == 7 then
		str = ADD_FOUR_PIXELS_CHARACTER .. str .. ADD_THREE_PIXELS_CHARACTER
	elseif missing_width == 8 then
		str = ADD_FOUR_PIXELS_CHARACTER .. str .. ADD_FOUR_PIXELS_CHARACTER
	elseif missing_width == 9 then
		str = ADD_FIVE_PIXELS_CHARACTER .. str .. ADD_FOUR_PIXELS_CHARACTER
	elseif missing_width >= 10 then
		local divisible_by_10_remainder = missing_width % 10
		if divisible_by_10_remainder == 9 then
			str = ADD_FIVE_PIXELS_CHARACTER .. str .. ADD_FOUR_PIXELS_CHARACTER
			missing_width = missing_width - 9
		elseif divisible_by_10_remainder == 8 then
			str = ADD_FOUR_PIXELS_CHARACTER .. str .. ADD_FOUR_PIXELS_CHARACTER
			missing_width = missing_width - 8
		elseif divisible_by_10_remainder == 7 then
			str = ADD_FOUR_PIXELS_CHARACTER .. str .. ADD_THREE_PIXELS_CHARACTER
			missing_width = missing_width - 7
		elseif divisible_by_10_remainder == 6 then
			str = ADD_THREE_PIXELS_CHARACTER .. str .. ADD_THREE_PIXELS_CHARACTER
			missing_width = missing_width - 6
		elseif divisible_by_10_remainder == 5 then
			str = ADD_THREE_PIXELS_CHARACTER .. str .. ADD_TWO_PIXELS_CHARACTER
			missing_width = missing_width - 5
		elseif divisible_by_10_remainder == 4 then -- 14 -> add 2, 2 -> remaining 5, 5
			str = ADD_TWO_PIXELS_CHARACTER .. str .. ADD_TWO_PIXELS_CHARACTER
			missing_width = missing_width - 4
		elseif divisible_by_10_remainder == 3 then -- 13 -> add 7, 6 -> remaining 0, 0
			str = ADD_FOUR_PIXELS_CHARACTER .. ADD_THREE_PIXELS_CHARACTER .. str .. ADD_THREE_PIXELS_CHARACTER .. ADD_THREE_PIXELS_CHARACTER
			missing_width = missing_width - 13
		elseif divisible_by_10_remainder == 2 then -- 12 -> add 6, 6 -> remaining 0, 0
			str = ADD_THREE_PIXELS_CHARACTER .. ADD_THREE_PIXELS_CHARACTER .. str .. ADD_THREE_PIXELS_CHARACTER .. ADD_THREE_PIXELS_CHARACTER
			missing_width = missing_width - 12
		elseif divisible_by_10_remainder == 1 then -- 11 -> add 6, 5 -> remaining 0, 0
			str =  ADD_THREE_PIXELS_CHARACTER .. ADD_THREE_PIXELS_CHARACTER .. str .. ADD_FIVE_PIXELS_CHARACTER
			missing_width = missing_width - 11
		end
		if (desired_pixel_width - get_pixel_width_of_string(str)) % 10 ~= 0 then
			msgbox('unexpected remainder for str ' .. tostring(str) .. ': ' .. tostring(desired_pixel_width - get_pixel_width_of_string(str)) % 10)
		end
		local to_add = string.rep(ADD_FIVE_PIXELS_CHARACTER, (missing_width) / 10)
		str = to_add .. str .. to_add
	end
	return str
end

function align(input, desired_pixel_width, alignment_type)
	if alignment_type ~= ALIGN_LEFT and alignment_type ~= ALIGN_RIGHT and alignment_type ~= ALIGN_MIDDLE then
		msgbox("ERROR: Unknown alignment type: " .. tostring(alignment_type))
		return tostring(input)
	end
	if desired_pixel_width < DISTANCE_BETWEEN_CHARACTERS then
		-- attempting this would lead to an infinite while loop!
		msgbox("ERROR: cannot set pixel width to less than " .. tostring(DISTANCE_BETWEEN_CHARACTERS) .. " for\n'" .. tostring(input) .. "'")
		return ''
	end
	local str = tostring(input)
	if str == nil then
		str = '~'
	end
	local current_width = get_pixel_width_of_string(str)
	local missing_width = desired_pixel_width - current_width
	-- allow a hierarchy of abbreviations, because why not
	while CUSTOM_ABBREVIATIONS[str] ~= nil and missing_width < 0 do
		str = CUSTOM_ABBREVIATIONS[str]		
		current_width = get_pixel_width_of_string(str)	
		missing_width = desired_pixel_width - current_width			
	end
	if missing_width < 0 then
		str = align(shorten_without_aligment(str, -missing_width, desired_pixel_width), desired_pixel_width, alignment_type)
	elseif alignment_type == ALIGN_LEFT then
		str = fill_up_string_left_aligned(str, missing_width, desired_pixel_width)
	elseif alignment_type == ALIGN_RIGHT then
		str = fill_up_string_right_aligned(str, missing_width, desired_pixel_width)
	elseif alignment_type == ALIGN_MIDDLE then
		str = fill_up_string_middle_aligned(str, missing_width, desired_pixel_width)
	end
	collectgarbage()
	return str
end

RANDOM_OPPONENTS_ID = 9

local function difficulty_int_to_name(difficulty)
	local string difficulty_string = "UNKNOWN"
	if difficulty == 0 then
		difficulty_string = "Tutor"
	elseif difficulty == 1 then
		difficulty_string = "Easy"
	elseif difficulty == 2 then
		difficulty_string = "Average"
	elseif difficulty == 3 then
		difficulty_string = "Hard"
	elseif difficulty == 4 then
		difficulty_string = "Impossible"
	end
	return difficulty_string
end

local function galaxy_size_int_to_name(size, is_cluster)
	local string size_string = "UNKNOWN"
	if size == 0 then
		size_string = "Small"
	elseif size == 1 then
		size_string = "Medium"
	elseif size == 2 then
		size_string = "Large"
	elseif size == 3 then
		if is_cluster == 1 then
			size_string = "Cluster"
		else
			-- huge is originally encoded with same size as cluster
			size = size + 1
			size_string = "Huge"
		end
	end
	return size_string
end

local function galaxy_age_int_to_name(age)
	local string age_string = "UNKNOWN"
	if age == 0 then
		age_string = "Mineral Rich"
	elseif age == 1 then
		age_string = "Average"
	elseif age == 2 then
		age_string = "Organic Rich"
	end
	return age_string
end

local function tech_level_int_to_name(level, is_postwarp)
	local string level_string = "UNKNOWN"
	if level == 0 then
		level_string = "Pre-warp"
	elseif level == 1 then
		if is_postwarp == 1 then
		    level = level + 1
			level_string = "Post-warp"
		else
			level_string = "Average"
		end
	elseif level == 2 then
		level = level + 1
		level_string = "Advanced"
	end
	return level_string
end

-- note: lua's string.gsub, string.gmatch and string.find methods are not supported
local function extract_mod_names(str)
	mod_names= ""
	if str == nil or #str == 0 then
		return "None"
	end
	last_backslash_index=1
	for i = 1, #str do
		if str:sub(i,i) == '\\' then
			last_backslash_index = i
		elseif str:sub(i,i) == ";" then
		    mod_names = mod_names .. string.sub(str, last_backslash_index + 1, i - 5) .. ", "
		    last_backslash_index = i
		end
	end
	if last_backslash_index > 1 then
		mod_names = mod_names .. string.sub(str, last_backslash_index + 1, - 5) .. ", "
	end
	return string.sub(mod_names, 0, #mod_names - 2)
end

local function nr_players_output(num_players)
	output = "UNKNOWN"
	if game.num_players == RANDOM_OPPONENTS_ID then
		output = "Random"
	else
		output = tostring(num_players)
	end
	return output
end

game = get_game()

local left_column_width = MAXIMUM_WIDTH_FOR_MSGBOX / 3
output = align("Game Information", MAXIMUM_WIDTH_FOR_MSGBOX, ALIGN_LEFT) .. "\n"
output = output .. "\n" .. align("Players", left_column_width, ALIGN_LEFT) .. nr_players_output(game.num_players)
output = output .. "\n" .. align("Difficulty", left_column_width, ALIGN_LEFT) .. difficulty_int_to_name(game.difficulty)
output = output .. "\n" .. align("Galaxy Size", left_column_width, ALIGN_LEFT) .. galaxy_size_int_to_name(game.galaxy_size, game.is_cluster)
output = output .. "\n" .. align("Galaxy Age", left_column_width, ALIGN_LEFT) .. galaxy_age_int_to_name(game.galaxy_age)
output = output .. "\n" .. align("Tech Level", left_column_width, ALIGN_LEFT) .. tech_level_int_to_name(game.civ, game.is_postwarp)
output = output .. "\n"
output = output .. "\n" .. align("Started with", left_column_width, ALIGN_LEFT) .. game.version_started
output = output .. "\n" .. align("Saved with", left_column_width, ALIGN_LEFT) .. game.version_saved
output = output .. "\n"
output = output .. "\n" .. align("Core mod", left_column_width, ALIGN_LEFT) .. tostring(get_conf("mod_name")) .. " (" .. tostring(get_conf("mod_id")) .. ")"
output = output .. "\n"
output = output .. "\n" .. "Pregame scripts\n" .. tostring(extract_mod_names(get_conf("pregame_script")))
output = output .. "\n"
output = output .. "\n" .. "Postgame scripts\n" .. tostring(extract_mod_names(get_conf("newgame_postprocessor_script")))

msgbox(output)