Custom_Message_Gauge = {
    default_title = "Incoming transmission",
    custom_hud_name_key_frame = "CustomMessage_Frame",
    custom_hud_name_key_body = "CustomMessage_Body",
    custom_hud_name_key_back = "CustomMessage_Back",
    custom_hud_name_key_name = "CustomMessage_Name",
    base_gauge_id = 27, --27 is message lines!

    match_voice_length = false,
    use_unicode_mode = false,
    default_speed = 0.01,
    default_length = 0.10,
    default_min_time = 0.25,
    max_line_width = 600,
    default_after_voice = 1.5,
    default_use_speed_variation = false,
    default_max_character_width = 85,

    disable_event_name = "UseOptimizedMessageGauge",
    min_duration_suffix = "MIN_MSG_TIME_"
}

--Code
	--toggles gauge
	function Custom_Message_Gauge:Gauge_Toggle(required_bool)

		if required_bool == true or required_bool == false then

            if (required_bool and hu.getHUDConfigShowStatus(self.base_gauge_id)) or (not required_bool) then
                mn.evaluateSEXP("( hud-set-custom-gauge-active ( "..tostring(required_bool).." ) !"..self.custom_hud_name_key_frame.."! )")
                mn.evaluateSEXP("( hud-set-custom-gauge-active ( "..tostring(required_bool).." ) !"..self.custom_hud_name_key_body.."! )")
                mn.evaluateSEXP("( hud-set-custom-gauge-active ( "..tostring(required_bool).." ) !"..self.custom_hud_name_key_back.."! )")
                mn.evaluateSEXP("( hud-set-custom-gauge-active ( "..tostring(required_bool).." ) !"..self.custom_hud_name_key_name.."! )")
            end

		end

	end

	--reset all 
    function Custom_Message_Gauge:ClearDynamic()

        self.Drawing = false
        self.Time_Paused = nil
        self.Duration = nil
        self.RawString = nil
        self.DisplayString = nil
        self.Timestamp = nil
        self.Draw_check_time = nil
        self.Current_Character = nil
        self.Total_Characters = nil
        self.Full_String_Shown = nil

        local gauge_frame = hu.getHUDGaugeHandle(self.custom_hud_name_key_frame)
        local gauge_body = hu.getHUDGaugeHandle(self.custom_hud_name_key_body)
        local gauge_back = hu.getHUDGaugeHandle(self.custom_hud_name_key_back)
        local gauge_name = hu.getHUDGaugeHandle(self.custom_hud_name_key_name)

        gauge_frame.Text = self.default_title
        gauge_body.Text = ""
        gauge_back.Text = ""
        gauge_name.Text = self.default_title

    end

    function Custom_Message_Gauge:HideDefault()
        mn.evaluateSEXP("(hud-set-builtin-gauge-active (false) !Messages!)")
    end

	function Custom_Message_Gauge:Reset()

        --recall StatusHelper (which sets visibility status) is run any time gameplay is being started again

        self.gauge_title = self.default_title
        self.custom_gauge_allowed = false

        self.Speed = self.default_speed
        self.Length = self.default_length
        self.UseSpeedVariation = self.default_use_speed_variation
        self.add_shipname_suffix = true --escort gauge also uses suffix on by default

        self:ClearDynamic()

        if mn.Events[self.disable_event_name]:isValid() or ba.MultiplayerMode then
            self.custom_gauge_allowed = false
        else
            if hu.getHUDConfigShowStatus(self.base_gauge_id) then
                self:HideDefault()
                self.custom_gauge_allowed = true
            else
                self.custom_gauge_allowed = false
            end
        end

	end

    --helps with toggling if actual message gauge disabled by player in HUD config
	function Custom_Message_Gauge:StatusHelper()

        if not self.Drawing then return end

		if hu.getHUDConfigShowStatus(self.base_gauge_id) then
            self.custom_gauge_allowed = true
		else
			self.custom_gauge_allowed = false
		end

		self:Gauge_Toggle(self.custom_gauge_allowed)

	end

    --setups message
    function Custom_Message_Gauge:SetupMessage(sender_str, message, messageText, sender_obj)

        --early outs
        if not self.custom_gauge_allowed or messageText == "" then return end

        sender_str = sender_str or ""

        --cleanup any overlapping messages
        self:Gauge_Toggle(false)
        self:ClearDynamic()

        --recall escort gauge and F4 messages log show display name
        --have sender name and display name both shown if specified

        --get sender names full
        local actual_name_suffix = ""
        local sender_name
        if self.add_shipname_suffix and sender_obj ~= nil and sender_obj:isValid() and sender_obj:getBreedName() == "Ship" then
            local actual_name = string.filterInvisible(sender_obj.Name)
            local display_name = sender_obj.DisplayName
            local suffix
            if display_name ~= nil and display_name ~= "" then
                --just use display name if FREDer is setting it
                sender_name = display_name
            elseif actual_name ~= sender_str then
                suffix = actual_name
            end

            if suffix ~= nil and suffix ~= "" and suffix ~= " " then
                sender_name = sender_str .." ("..suffix..")"
            end
        end

        self.Sender = sender_name or sender_str or ""

        --setup speed
        local thisMessage
        local speedFactor = 1

        if mn.Messages[message]:isValid() then
            thisMessage = mn.Messages[message]
        end

        if self.UseSpeedVariation then
            if string.find(message, "!!!", 1, true) == 1 then
                speedFactor = 0
            elseif string.find(message, "!!", 1, true) == 1 then
                speedFactor = 0.3
            elseif string.find(message, "!", 1, true) == 1 then
                speedFactor = 0.6
            elseif string.find(message, "...", 1, true) == 1 then
                speedFactor = 2.0
            elseif string.find(message, "..", 1, true) == 1 then
                speedFactor = 1.6
            elseif string.find(message, ".", 1, true) == 1 then
                speedFactor = 1.3
            end
        end

        --add line breaks according to max width!
        local function breakString(str, maxLength)
            local segments = {}
            local startPos = 1
            local str_len = #str

            while startPos <= str_len do
                local endPos = math.min(startPos + maxLength - 1, str_len)
                local segment = str:sub(startPos, endPos)

                -- Try to break at the last space within the segment
                local lastSpace = segment:match(".*()%s+[^%s]*$") -- position of last space
                if lastSpace and endPos < str_len then
                    endPos = startPos + lastSpace - 1
                end

                table.insert(segments, str:sub(startPos, endPos))
                startPos = endPos + 1

                -- Skip leading spaces in the next segment
                while str:sub(startPos, startPos):match("%s") do
                    startPos = startPos + 1
                end
            end

            return table.concat(segments, "\n")
        end

        --saved updated string with newlines
        self.RawString = breakString(messageText, self.default_max_character_width)

        self.DisplayString = nil
        self.Drawing = true

        self.Speed = self.default_speed
        self.Speed = self.Speed * speedFactor
        local guesstimatelength 

        local min_time = self.default_min_time
        local len_raw
        if self.use_unicode_mode then
            len_raw = utf8.len(self.RawString)
            guesstimatelength = math.max(self.Length * len_raw, self.Length * len_raw * speedFactor, min_time)
        else
            len_raw = #self.RawString
            guesstimatelength = math.max(self.Length * len_raw, self.Length * len_raw * speedFactor, min_time)
        end

        local m_time = mn.getMissionTime()

        local duration
        if thisMessage ~= nil then	
            --try voice file duration
            if thisMessage.VoiceFile.Duration > 0 then
                --"Got Voice" so set duration and speed
                duration = thisMessage.VoiceFile.Duration + self.default_after_voice
                if self.match_voice_length then
                    self.Speed = (duration / len_raw)*0.8
                end
            else
                --"No voice", so guess duration
                duration = guesstimatelength
            end

            --special case to force mission to display for longer or shorter
            local msg_name = thisMessage.Name
            local min_d_start, min_d_end = string.find(msg_name, self.min_duration_suffix)
            if msg_name ~= nil and min_d_end ~= nil then
                local duration_override = 0
                local du_over_txt = string.sub(msg_name, min_d_end+1, #msg_name)
                local du_over_num = tonumber(du_over_txt)
                if du_over_num ~= nil then
                    duration = du_over_num
                end
            end

        else
            --failsafe if message not valid
            duration = guesstimatelength
            --"Built in"
        end

        --update dynamic variables
        self.Duration = duration
        self.Timestamp = m_time
        self.Time_Paused = 0
        self.Draw_check_time = 0
        self.Current_Character = 0
        self.Total_Characters = len_raw
        self.Full_String_Shown = false

        --force gauge showing and update 
        --(otherwise might be a few frame flicker from old message that is still running on HUD frame)
        self:DrawUpdate(true)

        self:Gauge_Toggle(true)

    end

    --updates and shows message
    function Custom_Message_Gauge:DrawUpdate(force_draw_start)

        --early outs
        if not hu.HUDDrawn then return end
--      if hu.HUDDisabledExceptMessages then return end
        if not self.custom_gauge_allowed then return end
        if not self.Drawing then return end

        --setup
        local m_time = mn.getMissionTime()
        local timer_done = m_time > self.Timestamp + self.Duration + self.Time_Paused

        --done with duration so toggle off
        --make sure timer is up and full string shown
        --  timer can be up but need to finish the string
        --  string can be shown but need to finish the time 
		if timer_done and self.Full_String_Shown then
            self:Gauge_Toggle(false)
            self:ClearDynamic()
			return
		end

        --no need to update text if already at max, but we are not turning gauge off until timer done
        if self.Full_String_Shown then return end

        --only run 20 times a second for type writer effect update, more is not needed 
        if m_time > self.Draw_check_time then
            self.Draw_check_time = m_time + 0.05
        else
            return
        end

        local update_text = false

        --draw type writer effect
        if self.Speed == 0 then
            self.DisplayString = self.RawString
            self.Current_Character = self.Total_Characters
            update_text = true
        else
            local toDraw = ((m_time + self.Time_Paused) - self.Timestamp) / self.Speed

            --force drawing update if starting new message
            if force_draw_start then
                toDraw = 1
            end

            -- only update string if progressed since last update
            if toDraw > 0 and toDraw > self.Current_Character then

                self.Current_Character = toDraw

                --max current character to max characters
                if self.Current_Character > self.Total_Characters then
                    self.Current_Character = self.Total_Characters
                end

                if self.use_unicode_mode then
                    self.DisplayString = utf8.sub(self.RawString, 1, toDraw)
                else
                    self.DisplayString = self.RawString:sub(1, toDraw)
                end

                update_text = true

            end
        end

        --actually set text
        if update_text then

            if self.Current_Character >= self.Total_Characters then
                self.Full_String_Shown = true
            end

            local gauge_frame = hu.getHUDGaugeHandle(self.custom_hud_name_key_frame)
            local gauge_body = hu.getHUDGaugeHandle(self.custom_hud_name_key_body)
            local gauge_back = hu.getHUDGaugeHandle(self.custom_hud_name_key_back)
            local gauge_name = hu.getHUDGaugeHandle(self.custom_hud_name_key_name)

            gauge_frame.Text = self.Sender or ""
            gauge_body.Text = self.DisplayString or ""
            gauge_back.Text = self.DisplayString or ""
            gauge_name.Text = self.Sender or ""
        end

    end

    --update type writer if paused
    function Custom_Message_Gauge:OnPause()

        if self.Time_Paused and self.custom_gauge_allowed and self.Drawing then
            self.Time_Paused = self.Time_Paused + ba.getMissionFrametime()
        end

    end

--

--Hooks
if not IS_STANDALONE then 
	engine.addHook("On Gameplay Start", function() 
			Custom_Message_Gauge:Reset()
		end
	)

	engine.addHook("On Mission End", function() 
			Custom_Message_Gauge:Reset()
		end
	)

	engine.addHook("On State Start", function() 
            --On State Start runs before On Gameplay Start, 
            --  but On State Start runs every time we come back into the game, even from a paused menu
            --so we do not want to always run the setup scripts, 
            --  but we do want to check the draw HUD status and run a sexp if needed, but only after time has passed
            if MTIME_OVER_P1 then
                Custom_Message_Gauge:StatusHelper()
            end
		end,
		{State="GS_STATE_GAME_PLAY"}
	)

	engine.addHook("On Message Received", function() 
            Custom_Message_Gauge:SetupMessage(hv.SenderString, hv.Name, hv.Message, hv.Sender)
		end,
		{State="GS_STATE_GAME_PLAY"}
	)

	engine.addHook("On HUD Draw", function() 
			Custom_Message_Gauge:DrawUpdate()
		end,
		{State="GS_STATE_GAME_PLAY"}
	)

	engine.addHook("On Frame", function() 
			Custom_Message_Gauge:OnPause()
		end,
		{State="GS_STATE_GAME_PAUSED"}
	)
end
