-- return the number of keys in a given table (# including non-array entries)
local function keyCount( t )
	local count = 0
	for _, _ in pairs( t ) do
		count = count + 1
	end
	return count
end

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

local function countHits( hitLines, activeLines )
	local hits = 0
	local misses = 0
	for line in pairs( hitLines ) do
		if activeLines[ line ] then
			hits = hits + 1
		else
			misses = misses + 1
		end
	end
	
	return hits, misses
end

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

local accumulatorFunctions = { }

-- add given values to hit and active line counts
function accumulatorFunctions.increment( t, hit, active, missed )
	t.hit = t.hit or 0
	t.active = t.active or 0
	t.missed = t.missed or 0
	t.hit = t.hit + hit
	t.active = t.active + active
	t.missed = t.missed + missed
end

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

-- account for the data for a single chunk/file
function accumulatorFunctions:addChunk( partition, chunk, filepath, hitLines, activeLines )
	local index = self.index[ partition ]
	if not index then
		index = #self + 1
		self.index[ partition ] = index
		self[ index ] = { name = partition }
	end
	
	partition = self[ index ]
	
	local chunkData = {
		name = chunk,
		filepath = filepath,
		hitLines = hitLines,
		activeLines = activeLines
	}
	
	local active = keyCount( chunkData.activeLines )
	local hit, missed = countHits( chunkData.hitLines, chunkData.activeLines )
 	self.increment( chunkData, hit, active, missed )
	
	table.insert( partition, chunkData )

	-- rollups
	self.increment( partition, hit, active, missed )
 	self:increment( hit, active, missed )
end

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

local function formatOneChunk( t )
	local hit = t.hit or 0
	local active = t.active or 0

	local percent = active > 0 and ( hit  * 100.0 / active ) or 0

	return string.format( [[{
	    data: {
	        $area: %d,
	        $part: %d,
	        $color: %f,
	    },
	    id: "%s",
	    name: "%s"
	}]], active, hit, percent, t.filepath, t.name )
end

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

-- dump a summary of the coverage data to a JSON array (formatted for use
-- with a specific in browser TreeTable viewer library)
-- see experiments/coverage/viewer for more information.
function accumulatorFunctions:toJSON( emit )
	emit = emit or print

	emit "loadTreemap( {"
	emit "children: ["
	for j, part in ipairs( self ) do
		emit( j == 1 and "" or "," )
		emit "{"
		emit "children: ["
		for i, chunk in ipairs( part ) do
			emit( i == 1 and "" or "," )
			emit( formatOneChunk( chunk ) )
		end
		emit "],"
		emit( string.format( [[ id: "%s", name: "%s" ]], part.name, part.name ) )
		emit( "}" )
	end
	emit "],"
	emit( string.format( [[ id: "%s", name: "%s" ]], "ALL", "ALL" ) )
	emit( "} )" )
end

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

function accumulatorFunctions:setRawInput( t )
	self.rawinput = t
end

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

function accumulatorFunctions:getRawInput( t )
	return self.rawinput
end

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

-- table.shallowcopy is not available in standard table namespace
local function clone( t )
	local result = { }
	for k, v in pairs( t ) do
		result[ k ] = v
	end
	return result
end

local function coverageAccumulator()
	local accumulator = clone( accumulatorFunctions )
	accumulator.index = { }
	accumulator.name = "ALL"
	return accumulator
end

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

local function accumulateStatistics( chunkHitLines, scope, yielder, files, partitionFn, activeLineFn )
	
	local accumulator = coverageAccumulator()

	scope:setCaption( "Grouping coverage data by subsystem." )

	for i, filepath in ipairs( files ) do
		
		scope:setPortionComplete( i, #files )
		yielder( 0.1 )
				
		local partid, chunk = partitionFn( filepath )
		if partid and chunk then
			local activeLines = activeLineFn( filepath, chunk )
			accumulator:addChunk( partid, chunk, filepath, chunkHitLines[ chunk ] or { }, activeLines )
		end
		
		if scope:isCanceled() then
			break
		end
	end
	
	accumulator:setRawInput( chunkHitLines )
	
	return accumulator
end

--------------------------------------------------------------------------------
-- Customization for wichitafoundation headless loader
--------------------------------------------------------------------------------

local function makeRelative( a, b )
	local result
	a = string.gsub( a, "/*$", "" )
	b = string.gsub( b, "/*$", "" )
	if string.sub( a, 1, #b ) == b and string.sub( a, #b+1,#b+1 ) == '/' then
		result = string.sub( a, #b+2 )
	end
	return result or a
end

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

-- defines the way that the Lua files are grouped for coverage rollups
local function getBaseDir()

	local coveragePath = os.getenv( "COVERAGE_TOOLKIT" )
	local baseMainDir = string.match( coveragePath, "(.*)/build" )
	return baseMainDir
	
end

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

local function formatOneChunk_Wichita( t )
	local hit = t.hit or 0
	local active = t.active or 0

	local percent = active > 0 and ( hit  * 100.0 / active ) or 0
	local id = makeRelative( t.filepath, getBaseDir() )
	return string.format( [[{
	    "data": {
	        "active": "%d",
	        "hit": "%d",
	        "percent": "%f"
	    },
	    "id": "%s",
	    "name": "%s"
	}]], active, hit, percent, id, t.name )
end

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

function accumulatorFunctions:toJSON_Wichita( emit )
	emit = emit or print

	emit "{"
	emit '"coverage": ['
	local totalHit = 0
	local totalActive = 0
	for j, part in ipairs( self ) do
		emit( j == 1 and "" or "," )
		emit "{"
		emit '"subsystem": ['
		for i, chunk in ipairs( part ) do
			emit( i == 1 and "" or "," )
			emit( formatOneChunk_Wichita( chunk ) )
		end
		emit "],"
		totalHit = totalHit + part.hit
		totalActive = totalActive + part.active
		local percent = part.active > 0 and ( part.hit  * 100.0 / part.active ) or 0
		emit( string.format( [[ "name": "%s", "active": "%s", "hit": "%s", "percent": "%s"  ]], part.name, part.active, part.hit, percent ) )
		emit( "}" )
	end
	emit "],"
	local totalPercent  = totalActive > 0 and ( totalHit  * 100.0 / totalActive ) or 0
	emit( string.format( [[ "name": "wichitafoundation", "active": "%s", "hit": "%s", "percent": "%s"  ]], totalActive, totalHit, totalPercent ) )

	emit( "}" )
end

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

return {
	accumulateStatistics = accumulateStatistics
}
