local function treeAccumulator( t, pc, hash, base )
	local tpc = t[ pc ]
	if not tpc then
		tpc = { }
		t[ pc ] = tpc
	end

	if base then
		local thash = tpc[ hash ]
		if not thash then
			thash = { }
			tpc[ hash ] = thash
		end
		
		thash[ base ] = 1
	else
		tpc[ hash ] = 1
	end
end

local instructionFramePattern = "^([0-9]+):([0-9]+)%+([0-9]+)"

local function extractInstructionCoverage( tree, line )
	local hash, base, pc = string.match( line, instructionFramePattern )
	-- AgAlwaysPrint( "instruction", line )
	if hash then
		-- AgAlwaysPrint( "instruction match", tonumber( pc ), tonumber( hash ), tonumber( base ) )
		treeAccumulator( tree, tonumber( pc ), tonumber( hash ), tonumber( base ) )
	end
end

--------------------------------------------------------------------------------

local chunkLineFramePattern = "^([^:#]*):([0-9]+)"
local lastChunk = nil
local function extractLineCoverage( tree, line )
	local chunk, lineNumber = string.match( line, chunkLineFramePattern )
	if chunk == "" then
		chunk = lastChunk
	else
		lastChunk = chunk
	end
	-- AgAlwaysPrint( "line", line, chunk, lineNumber )
	if chunk then
		treeAccumulator( tree, tonumber( lineNumber ), chunk )
	end
end

--------------------------------------------------------------------------------

local function makeAutoDetectLineProcessor()
	local subProcessor

	return function( tree, line )
		if subProcessor then
			subProcessor( tree, line )
		elseif next( tree ) == nil then
			-- note that the line coverage pattern will match instruction coverage
			-- lines (but not vice versa), so test for instruction coverage first
			extractInstructionCoverage( tree, line )
			if next( tree ) then
				subProcessor = extractInstructionCoverage
				tree[ 0 ] = tree[ 0 ] or { }
			else
				extractLineCoverage( tree, line )
				if next( tree ) then
					subProcessor = extractLineCoverage
					for i = 1, 8192 do
						tree[ i ] = tree[ i ] or { }
					end
				end
			end
		else
			-- line and instruction coverage do not mix, so only allow the
			-- detection to occur once and balk at detecting for a non-empty
			-- container which may already have mismatched data
			error( "Auto detection should be done once per accumulator." )
		end
	end
end

--------------------------------------------------------------------------------

local function loadCoverageFile( filename, progress, optThrottle )
	local results = { }
	local lineProcessor = makeAutoDetectLineProcessor()
	optThrottle = optThrottle or function() end

	local f, errMessage = io.open( filename, "r" )

	if not f then
		return nil, errMessage
	end

	local size = f:seek( "end" )

	local function updateProgress()
		-- The file may be growing in size (particularly if LR is still appending
		-- to it), but do not report more than its original estimated size as that
		-- will throw off progress and eventually cause it to be reported as more
		-- than 100% done, which will throw an error.
		progress( math.min( f:seek(), size ), size )
	end

	f:seek( "set" )
	for line in f:lines() do
		lineProcessor( results, line )
		-- SPAM [dtull 2009-08-21]: For reasons I do not fully understand, the
		-- Windows implementation of the seek function can cause the file cursor
		-- to go backward one line, which in combination with the lines() iterator
		-- causes an infinite loop.  For now, the Windows version will only show
		-- progress once per file.
		optThrottle( updateProgress )
	end
	updateProgress()
	
	return results
end


--==============================================================================

return {
	loadCoverageFile = loadCoverageFile,
}
