--!strict

-- This ModuleScript provides a function to decode binary run-length encoded (RLE) image data.
--
-- To use this module:
-- 1. Save this code as a ModuleScript in your Roblox project (e.g., in ReplicatedStorage).
-- 2. In another script, require it: `local RLEDecoder = require(game.ReplicatedStorage.RLEDecoder)`
-- 3. Call the `decode` function with your binary RLE string and a pre-allocated buffer:
--    `local success = RLEDecoder.decode(your_binary_rle_string, your_frame_buffer, image_width, image_height)`

local RLEDecoder = {}

-- decode
-- Decodes a binary string containing run-length encoded (RLE) image data
-- directly into a provided framebuffer (buffer type) in RGBA format.
--
-- The expected binary format for each run is:
-- - 2 bytes: Run length (u16, little-endian)
-- - 3 bytes: R, G, B color (u8, u8, u8)
--
-- Args:
--   binary_data_string: The binary string containing the RLE data.
--   framebuffer: A pre-allocated Luau buffer where the RGBA pixel data will be written.
--                It must be large enough to hold `width * height * 4` bytes.
--   width: The width of the image in pixels.
--   height: The height of the image in pixels.
--
-- Returns:
--   boolean: true if decoding was successful and the framebuffer was written,
--            false if the framebuffer is too small or input data is invalid.
function RLEDecoder.decode(binary_data_string: string, framebuffer: buffer, width: number, height: number): boolean
	local current_pos: number = 1
	local data_len: number = #binary_data_string
	local pixel_count: number = 0 -- Tracks the number of pixels written to the framebuffer

	-- Each RLE entry is 2 bytes for length + 3 bytes for color = 5 bytes
	local ENTRY_SIZE: number = 5
	local PIXEL_SIZE_RGBA: number = 4 -- RGBA format (R, G, B, A)

	-- Check if the provided framebuffer is large enough
	local required_buffer_size = width * height * PIXEL_SIZE_RGBA
	if buffer.len(framebuffer) < required_buffer_size then
		warn(
			"RLEDecoder.decode: Provided framebuffer is too small. Required:",
			required_buffer_size,
			"Got:",
			buffer.len(framebuffer)
		)
		return false
	end

	-- Loop through the binary data
	while current_pos <= data_len - (ENTRY_SIZE - 1) do
		-- Read the 2-byte run length (u16, little-endian)
		-- The format string "<H" means unsigned short, little-endian
		local run_length: number = string.unpack("<H", binary_data_string, current_pos)
		current_pos += 2 -- Move past the length bytes

		-- Read the 3-byte RGB color (u8, u8, u8)
		-- The format string "BBB" means three unsigned bytes
		local r: number, g: number, b: number = string.unpack("BBB", binary_data_string, current_pos)
		current_pos += 3 -- Move past the color bytes

		-- Write the RGB color (with alpha 255) to the framebuffer 'run_length' times
		for _ = 1, run_length do
			local offset = pixel_count * PIXEL_SIZE_RGBA -- Calculate offset for RGBA pixel

			-- Ensure we don't write past the end of the buffer
			if offset + PIXEL_SIZE_RGBA > buffer.len(framebuffer) then
				warn(
					"RLEDecoder.decode: Buffer overflow detected during decoding. Data might be malformed or dimensions incorrect."
				)
				return false
			end

			buffer.writeu8(framebuffer, offset, r)
			buffer.writeu8(framebuffer, offset + 1, g)
			buffer.writeu8(framebuffer, offset + 2, b)
			buffer.writeu8(framebuffer, offset + 3, 255) -- Alpha channel set to 255 (fully opaque)

			pixel_count += 1
		end
	end

	-- Optional: Check if the total pixels written matches expected dimensions
	if pixel_count ~= (width * height) then
		warn(
			"RLEDecoder.decode: Decoded pixel count (",
			pixel_count,
			") does not match expected dimensions (",
			width * height,
			"). Data might be incomplete or malformed."
		)
		-- Decide if this should be a hard fail or just a warning based on application needs
		-- return false
	end

	return true -- Successfully decoded and wrote to framebuffer
end

return RLEDecoder
