-- Utility Functions

function CreateCharRef( character )

	if character == nil then
		return
	end

	local charRef = {}
	charRef.TeamIndex = character.TeamIndex
	charRef.CharacterIndex = character.CharacterIndex
	return charRef

end

function GetCharByRef( ref )

	if ref == nil then
		return nil
	end

	return League[ref.TeamIndex].TeamBench[ref.CharacterIndex]

end

function TableLength( table )

	if table == nil then
		return
	end

	local count = 0
	for _ in pairs( table ) do
		count = count + 1
	end
	return count + 0

end

function CalcTotalNumEntries( table )

	if table == nil then
		return
	end

	local count = 0
	for key, value in pairs( table ) do
		count = count + 1
		if type( value ) == "table" then
			count = count + CalcTotalNumEntries( value )
		end
	end

	return count

end

function ShallowCopyTable( table )

	if table == nil then
		return
	end

	local copy = {}
	for k, v in pairs( table ) do
		copy[k] = v
	end
	return copy
end

function DeepCopyTable( orig )
	local orig_type = type(orig)
	local copy
	if orig_type == 'table' then
		copy = {}
		-- slightly more efficient to call next directly instead of using pairs
		for k,v in next, orig, nil do
			copy[k] = DeepCopyTable(v)
		end
	else
		copy = orig
	end

	return copy
end

function GetAllKeys( tableToGather )

	if tableToGather == nil then
		return
	end

	local keys = {}
	for k, v in pairs( tableToGather ) do
		table.insert( keys, k )
	end
	return keys
end

function MergeTables( table1, table2 )

	if table1 == nil or table2 == nil then
		return
	end

	local returnTable = ShallowCopyTable( table1 )
	for k, v in pairs( table2 ) do
		returnTable[k] = v
	end

	return returnTable

end

function CombineTables( table1, table2 )

	if table1 == nil or table2 == nil then
		return
	end

	local returnTable = ShallowCopyTable( table1 )
	for k, v in pairs( table2 ) do
		table.insert( returnTable, v )
	end

	return returnTable

end

function CombineAllValues( tables )

	if tables == nil then
		return
	end

	local returnTable = {}
	for k, subTable in pairs( tables ) do
		for k, value in pairs( subTable ) do
			table.insert( returnTable, value )
		end
	end

	return returnTable

end

function ConcatTableValues( fromTable, intoTable )
	for k, v in pairs( fromTable ) do
		table.insert(intoTable, v)
	end
end

function CollapseTable( table )

	if table == nil then
		return
	end

	local collapsedTable = {}
	local index = 1
	for k, v in pairs( table ) do
		collapsedTable[index] = v
		index = index + 1
	end

	return collapsedTable

end

function RemoveFromList( table, valueToRemove )

	if table == nil or valueToRemove == nil then
		return false
	end

	for key, value in pairs( table ) do

		if value == valueToRemove then
			table[key] = nil
			return true
		end
	end

	return false

end

function ContainsAny( tableToSearch, tableOfValues )

	for k, v in pairs(tableOfValues) do
		if Contains(tableToSearch, v) then
			return true
		end
	end

	return false

end

function DebugContains( table, value )

	if table == nil or value == nil then
		DebugPrint{ Text = "nil table or value" }
		return
	end

	for key, tableValue in pairs( table ) do

		DebugPrint({ Text = "Key = "..tostring(key).." Value = "..tostring(tableValue) })
		if tableValue == value then
			return true
		end

	end

	return false

end

function RemoveValuesFromTable( tableToSearch, valuesToRemove )

	if tableToSearch == nil or valuesToRemove == nil then
		return
	end

	for key1, value1 in pairs( valuesToRemove ) do

		index = 1
		for key2, value2 in pairs( tableToSearch ) do

			if value1 == value2 then
				table.remove( tableToSearch, index )
				break
			end

			index = index + 1
		end

	end
end

function FindMatchingPair( searchId, pairsTable )

	if pairsTable == nil then
		return
	end

	local matchingPair = 0

	for id1, id2 in pairs( pairsTable ) do

		if searchId == id1 then
			matchingPair = id2
		end

		if searchId == id2 then
			matchingPair = id1
		end
	end

	return matchingPair

end

function CountKeys( table )

	if table == nil then
		return
	end

	local count = 0
	for k,v in pairs( table ) do
		count = count + 1
	end
	return count
end

function IsEmpty( table )

	if table == nil then
		return true
	end

	return next(table) == nil
end

function Clear( table )
	for key in pairs( table ) do
		table[key] = nil
	end
end

function RemoveRandomValue( table )

	if table == nil then
		return
	end

	local numItems = TableLength( table )
	local randomIndex = math.random( numItems )

	local index = 0
	for key, value in pairs( table ) do

		index = index + 1
		if index == randomIndex then
			table[key] = nil
			return value
		end
	end

end

function RemoveRandomValueIPairs( table )

	if table == nil then
		return
	end

	local numItems = TableLength( table )
	local randomIndex = math.random( numItems )

	local index = 0
	for key, value in ipairs( table ) do

		index = index + 1
		if index == randomIndex then
			table[key] = nil
			return value
		end
	end

end

function RemoveFirstValue( table )

	if table == nil then
		return
	end

	for key, value in pairs( table ) do
		table[key] = nil
		return value
	end

end

function RemoveFirstIndexValue( table )

	-- Remove the value at index 1 and bump the rest of the table up

	if table == nil or IsEmpty( table ) then
		return
	end

	local returnValue = table[1]
	for index, value in ipairs( table ) do
		if table[index + 1] ~= nil then
			table[index] = table[index + 1]
		end
	end
	table[#table] = nil

	if returnValue == nil then
		-- Backwards compatibility for tables that were previously not properly collapsed
		returnValue = RemoveFirstValue( table )
	end

	return returnValue

end

function RemoveValue( table, value )

	if table == nil or value == nil then
		return
	end

	for key, tableValue in pairs( table ) do

		if value == tableValue then
			table[key] = nil
			return value
		end
	end

end

function RemoveValueAndCollapse( table, value )

	if table == nil or value == nil then
		return false
	end

	local foundValue = false
	for index, tableValue in ipairs( table ) do
		if not foundValue and value == tableValue then
			table[index] = nil
			foundValue = true
		end
		if foundValue then
			local nextIndex = index + 1
			if table[nextIndex] ~= nil then
				table[index] = table[nextIndex]
			else
				-- Remove the final index that has now been moved up
				table[index] = nil
			end
		end
	end

	return foundValue

end

function RemoveAllValues( table, value )

	if table == nil or value == nil then
		return
	end

	for key, tableValue in pairs( table ) do
		if tableValue == value then
			table[key] = nil
		end
	end

end

function GetRandomValue( table )

	if table == nil then
		return
	end

	local numItems = TableLength( table )
	local randomIndex = math.random( numItems )

	local index = 0
	for key, value in pairs( table ) do

		index = index + 1
		if index == randomIndex then
			return value
		end
	end

end

function GetRandomKey( table )

	if table == nil then
		return
	end

	local numItems = TableLength( table )
	local randomIndex = math.random( numItems )

	local index = 0
	for key, value in pairs( table ) do

		index = index + 1
		if index == randomIndex then
			return key
		end
	end

end

function RemoveRandomKey( table )

	if table == nil then
		return
	end

	local numItems = TableLength( table )
	local randomIndex = math.random( numItems )

	local index = 0
	for key, value in pairs( table ) do

		index = index + 1
		if index == randomIndex then
			table[key] = nil
			return key
		end
	end

end

function GetKey( table, value )

	if table == nil then
		return
	end

	for k, v in pairs( table ) do

		if v == value then
			return k
		end
	end

end

function Lerp( start, finish, fraction )
      return start + (finish - start) * fraction
end

function RandomFloat( low, high )

	local randomFloat = low + ( math.random() * (high - low) )
	return randomFloat

end

function RandomChance( chance )

	if math.random() <= chance then
		return true
	end
	return false

end

function CoinFlip()

	if math.random() > 0.5 then
		return true
	else
		return false
	end

end

function CountValues( table, value )

	if table == nil or value == nil then
		return
	end

	local countValues = 0
	for key, tableValue in pairs( table ) do

		if tableValue == value then
			countValues = countValues + 1
		end
	end
	--DisplayInfoPanelText({ Name = tostring(countValues) })
	return countValues

end

function GetAverageDistance( table )

	if table == nil then
		return
	end

	local total = 0
	local numObjects = #table
	local average = 0

	for index = 1, numObjects, 1 do
		if table[index + 1] ~= nil then
			local distanceBetweenTargets = GetDistance({ Id = table[index], DestinationId = table[index + 1] })
			total = total + distanceBetweenTargets
		end
	end
	average = total / numObjects
	if average ~= nil then
		return average
	end

end

function GetMaxDistance( table )

	if table == nil then
		return
	end

	local total = 0
	local numObjects = #table
	local highest = 0
	local distanceBetweenTargets = 0

	for k, v in pairs(table) do
		for key, value in pairs(table) do
			distanceBetweenTargets = GetDistance({ Id = v, DestinationId = value })
			if distanceBetweenTargets > highest then
				highest = distanceBetweenTargets
			end
		end
	end

	return highest

end

OnAnyLoad{ function( triggerArgs )

	SpeechRecord = SpeechRecord or {}
	PersistVariable({ Name = "SpeechRecord" })

	RemainingCues = RemainingCues or {}
	PersistVariable({ Name = "RemainingCues" })

	RemainingEvents = RemainingEvents or {}
	PersistVariable({ Name = "RemainingEvents" })

	RemainingData = RemainingData or {}
	PersistVariable({ Name = "RemainingData" })

	InfoPanelRecord = InfoPanelRecord or {}
	PersistVariable({ Name = "InfoPanelRecord" })

end }

function PlaySpeechAndWait( T )
	-- Note that "Name" becomes "Name1" because of ScriptManager mangling, but it is
	-- passed through as 'Name' in the "Error Check" case in the editor
	local name = T.Name1 or T.Name
	assert(name, "PlaySpeechAndWait requires the 'Name' argument")
	if T.Delay and T.Delay > 0 then
		wait(T.Delay)
		T.Delay = nil
	end
	local res = PlaySpeechOnce( T )
	if res then
		waitUntil(name)
	end
	return res
end

function PlaySpeechOnce( T )
	-- Note that "Name" becomes "Name1" because of ScriptManager mangling, but it is
	-- passed through as 'Name' in the "Error Check" case in the editor
	local cueName = T.Name1 or T.Name
	assert(cueName, "PlaySpeechOnce requires the 'Name' argument")
	if SpeechRecord[cueName] == nil and PlaySpeechNow( T ) then
		SpeechRecord[cueName] = true
		return true
	end

	return false
end

function CycleSpeechTable( speechTable, queueType )

	if #speechTable <= 0 then
		return
	end

	local cueName = speechTable[1]
	PlaySpeech({ Name = cueName, Queue = queueType })
	SpeechRecord[cueName] = true

	-- Move to back of list
	table.remove( speechTable, 1 )
	table.insert( speechTable, cueName )

end

function PlayRemainingSpeech( table, args, queueType )

	-- @refactor Using "first" item in the data table as the shared key -- assumes tables don't share items which is currently true
	local tableKey = table[1]

	if RemainingCues[tableKey] == nil or IsEmpty( RemainingCues[tableKey] ) then
		-- Refill the remaining table
		RemainingCues[tableKey] = ShallowCopyTable( table )
	end

	local cueName = RemoveRandomValue( RemainingCues[tableKey] )
	if args == nil then
		PlaySpeech({ Name = cueName, Queue = queueType })
		SpeechRecord[cueName] = true
	elseif args == "noreload" then
		PlaySpeechOnce({ Name = cueName })
	end
	-- used for some functions
	lastCuePlayed = cueName
	
	return cueName

end

function ResetRemainingSpeech( table )

	-- @refactor Using "first" item in the data table as the shared key -- assumes tables don't share items which is currently true
	local tableKey = table[1]
	-- Refill the remaining table
	RemainingCues[tableKey] = ShallowCopyTable( table )

end

function PlayNextRemainingSpeech( table, args, queueType )

	-- @refactor Using "first" item in the data table as the shared key -- assumes tables don't share items which is currently true
	local tableKey = table[1]

	if RemainingCues[tableKey] == nil or IsEmpty(RemainingCues[tableKey]) then
		-- Refill the remaining table
		RemainingCues[tableKey] = ShallowCopyTable( table )
	end

	local cueName = RemoveFirstIndexValue( RemainingCues[tableKey] )
	if args == nil then
		PlaySpeech({ Name = cueName, Queue = queueType })
		SpeechRecord[cueName] = true
	elseif args == "noreload" then
		PlaySpeechOnce({ Name = cueName })
	end

	return cueName

end

-- variation of previous used with archetype speech variations
function PlayRemainingSpeechWithPrefix( table, args, prefix )

	if prefix == nil then
		return
	end

	local tableKey = table[1]

	if RemainingCues[tableKey] == nil or IsEmpty(RemainingCues[tableKey]) then
		-- Refill the remaining table
		RemainingCues[tableKey] = ShallowCopyTable( table )
	end

	local cueSuffix = RemoveRandomValue( RemainingCues[tableKey] )
	local cueName = prefix..cueSuffix
	lastCueName = cueName
	if args == nil then
		PlaySpeech({ Name = cueName, Queue = "False" })
		SpeechRecord[cueName] = true
	elseif args == "noreload" then
		PlaySpeechOnce({ Name = cueName })
	end

	return cueName

end

function GetRemainingEvents( table )

	if table == nil or IsEmpty( table ) then
		return nil
	end

	-- @refactor Using "first" item in the data table as the shared key -- assumes tables don't share items which is currently true
	local tableKey = table[1]

	if RemainingEvents[tableKey] == nil or IsEmpty( RemainingEvents[tableKey] ) then
		-- Refill the remaining table
		RemainingEvents[tableKey] = ShallowCopyTable( table )
	end

	return RemainingEvents[tableKey]

end

function GetRandomRemainingEvent( table )

	if table == nil or IsEmpty( table ) then
		return nil
	end

	-- @refactor Using "first" item in the data table as the shared key -- assumes tables don't share items which is currently true
	local tableKey = table[1]

	if RemainingEvents[tableKey] == nil or IsEmpty( RemainingEvents[tableKey] ) then
		-- Refill the remaining table
		RemainingEvents[tableKey] = ShallowCopyTable( table )
	end

	local event = RemoveRandomValue( RemainingEvents[tableKey] )
	return event

end

function GetNextRemainingEvent( table )

	if table == nil or IsEmpty( table ) then
		return nil
	end

	-- @refactor Using "first" item in the data table as the shared key -- assumes tables don't share items which is currently true
	local tableKey = table[1]

	if RemainingEvents[tableKey] == nil or IsEmpty( RemainingEvents[tableKey] ) then
		-- Refill the remaining table
		RemainingEvents[tableKey] = ShallowCopyTable( table )
	end

	local event = RemoveFirstIndexValue( RemainingEvents[tableKey] )
	return event

end

function SimpleHash( value )

	if value == nil or type(value) == "function" then
		return ""
	end

	-- If the value is a table, collect its primitives into a string
	if type(value) == "table" then
		local simpleHash = ""
		for key, subValue in pairs( value ) do
			simpleHash = simpleHash..SimpleHash( subValue )
		end
		return simpleHash
	end

	if type(value) == "boolean" then
		return tostring(value)
	end

	-- If the value is a primitive, use itself
	return value

end

function GetRemainingEvent( table, event )

	-- @refactor Using "first" item in the data table as the shared key -- assumes tables don't share items which is currently true
	local tableKey = table[1]

	if RemainingEvents[tableKey] == nil or IsEmpty(RemainingEvents[tableKey]) then
		-- Refill the remaining table
		RemainingEvents[tableKey] = ShallowCopyTable( table )
	end

	local event = RemoveValue( RemainingEvents[tableKey], event )
	return event

end

function AddEvent( eventTable, event )

	if eventTable == nil or event == nil then
		return
	end

	if Contains( eventTable, event ) then
		-- No dupes
		return
	end

	table.insert( eventTable, event )

	-- @refactor Using "first" item in the data table as the shared key -- assumes tables don't share items which is currently true
	local tableKey = eventTable[1]
	if RemainingEvents[tableKey] == nil or IsEmpty(RemainingEvents[tableKey]) then
		-- Refill the remaining table
		RemainingEvents[tableKey] = ShallowCopyTable( eventTable )
	else
		table.insert( RemainingEvents[tableKey], event )
	end

end

function RemoveEvent( eventTable, event )

	if event == nil then
		return
	end

	if IsEmpty( eventTable ) then
		return
	end

	local tableKey = eventTable[1]

	RemoveFromList( eventTable, event )

	if IsEmpty( eventTable ) then
		-- Last event was removed, simply clear the remaining events
		RemainingEvents[tableKey] = {}
		return
	end

	if eventTable[1] == nil then
		-- The shared key was removed, must replace
		for key, value in pairs( eventTable ) do
			-- Move first found item up to index 1
			local newTableKey = value
			eventTable[1] = newTableKey
			eventTable[key] = nil
			-- Update the remainingEvents table likewise
			RemainingEvents[newTableKey] = RemainingEvents[tableKey]
			RemainingEvents[tableKey] = nil
			tableKey = newTableKey
			break
		end
	end

	-- @refactor Using "first" item in the data table as the shared key -- assumes tables don't share items which is currently true
	if RemainingEvents[tableKey] == nil or IsEmpty( RemainingEvents[tableKey] ) then
		-- Refill the remaining table
		RemainingEvents[tableKey] = ShallowCopyTable( eventTable )
	else
		RemoveFromList( RemainingEvents[tableKey], event )
	end

end

function GetRandomRemainingData( table, tableName )

	if table == nil then
		return nil
	end

	local tableKey = tableName
	if tableKey == nil then
		-- @refactor Using "first" item in the data table as the shared key -- assumes tables don't share items which is currently true
		tableKey = SimpleHash( table[1] )
	end

	if RemainingData[tableKey] == nil or IsEmpty( RemainingData[tableKey] ) then
		-- Refill the remaining table with every key from the original table
		RemainingData[tableKey] = GetAllKeys( table )
	end

	local randomKey = RemoveRandomValue( RemainingData[tableKey] )
	local data = table[randomKey]
	return data

end

function RandomTableCycle( tableToCycle )

	if tableToCycle == nil then
		return
	end

	local numCycles = math.random( #tableToCycle )
	for index = 1, numCycles, 1 do
		local frontItem = tableToCycle[1]
		table.remove( tableToCycle, 1 )
		table.insert( tableToCycle, frontItem )
	end

end

function DisplayInfoPanelOnce( args )
	if not InfoPanelRecord[args.Text] then
		DisplayInfoPanelText( args )
		InfoPanelRecord[args.Text] = true
		return true
	end

	return false
end

function CalcOffset( angle, distance )

	offset = {}

	offset.X = math.cos( angle ) * distance
	offset.Y = -math.sin( angle ) * distance

	return offset

end

function round( num, idp )
	local mult = 10^(idp or 0)
	return math.floor(num * mult + 0.5) / mult
end

function stringends(String,End)
	return End=='' or string.sub(String,-string.len(End))==End
end

function isint(n)
  return n==math.floor(n)
end


function Clamp( number, low, high )

	if number < low then
		return low
	elseif number > high then
		return high
	end

	return number

end

-- http://www.splinter.com.au/converting-hsv-to-rgb-colour-using-c/
function HSVtoRGB( h, S, V )

	local H = h
	while( H < 0 ) do
		H = H + 360
	end
	while( H >= 360 ) do
		H = H - 360
	end
	local R, G, B = 0
	if( V <= 0 ) then
		R = 0
		G = 0
		B = 0
	elseif( S <= 0 ) then
		R = V
		G = V
		B = V
	else
		local hf = H / 60.0
		local i = math.floor( hf )
		local f = hf - i;
		local pv = V * (1 - S)
		local qv = V * (1 - S * f);
		local tv = V * (1 - S * (1 - f))

		-- Red is the dominant color
		if i == 0 then
			R = V;
			G = tv;
			B = pv;
		-- Green is the dominant color
		elseif i == 1 then
			R = qv;
			G = V;
			B = pv;
		elseif i == 2 then
			R = pv;
			G = V;
			B = tv;
		-- Blue is the dominant color
		elseif i == 3 then
			R = pv;
			G = qv;
			B = V;
		elseif i == 4 then
			R = tv;
			G = pv;
			B = V;
		-- Red is the dominant color
		elseif i == 5 then
			R = V;
			G = pv;
			B = qv;
		-- Just in case we overshoot on our math by a little, we put these here. Since its a switch it won't slow us down at all to put these here.
		elseif i == 6 then
			R = V;
			G = tv;
			B = pv;
		elseif i == -1 then
			R = V;
			G = pv;
			B = qv;
		-- The color is not defined, we should throw an error.
		end
	end
	local r = ColorClamp( R * 255.0 )
	local g = ColorClamp( G * 255.0 )
	local b = ColorClamp( B * 255.0 )
	return { r, g, b, 255 }

end

function ColorClamp( i )

	if i < 0 then
		return 0
	elseif i > 255 then
		return 255
	else
		return i
	end

end

function Contains( table, value )

	if table == nil or value == nil then
		return
	end

	for key, tableValue in pairs( table ) do

		if tableValue == value then
			return true
		end
	end

	return false

end

function FlashLightBar( playerIndex, color, duration, frequency )

	local frequency = frequency or 25 -- default to 25hz
	local period = 1.0 / frequency
	local count = (duration * frequency) / 2
	for i=1, count do
		SetLightBarColor({ PlayerIndex = playerIndex, Color = color })
		wait( period )
		SetLightBarColor({ PlayerIndex = playerIndex, Color = {15, 15, 15} })
		wait( period )
	end

	SetLightBarColor({ PlayerIndex = playerIndex, Color = color })

end

GlobalCooldowns = {}

function CheckCooldown( name, time )

	if GlobalCooldowns[name] == nil then
		GlobalCooldowns[name] = -999
	end

	if _worldTime > GlobalCooldowns[name] + time then
		GlobalCooldowns[name] = _worldTime
		return true
	end

	return false

end

function AllRumble( params )

	local params2 = ShallowCopyTable( params )

	params.PlayerIndex = 1
	Rumble( params )
	
	params2.PlayerIndex = 2
	Rumble( params2 )

end

function SetMaskColors( objectId, colorObject )

	if colorObject == nil then
		return
	end

	SetPlayerUnitProperty({ DestinationId = objectId, Name = "MaskHue", Value = colorObject.MaskHue })
	SetPlayerUnitProperty({ DestinationId = objectId, Name = "MaskHue2", Value = colorObject.MaskHue2 })
	SetPlayerUnitProperty({ DestinationId = objectId, Name = "MaskSaturationAddition", Value = colorObject.MaskSaturationAddition })
	SetPlayerUnitProperty({ DestinationId = objectId, Name = "MaskSaturationAddition2", Value = colorObject.MaskSaturationAddition2 })
	SetPlayerUnitProperty({ DestinationId = objectId, Name = "MaskValueAddition", Value = colorObject.MaskValueAddition })
	SetPlayerUnitProperty({ DestinationId = objectId, Name = "MaskValueAddition2", Value = colorObject.MaskValueAddition2 })
end

AchivementUnlocks = {}

function UnlockAchievementOnce( achievementName )
	if not AchivementUnlocks[achievementName] then
		UnlockAchievement({ Name = achievementName })
		AchivementUnlocks[achievementName] = true
	end
end

-- https://www.rosettacode.org/wiki/Strip_a_set_of_characters_from_a_string#Lua
function StripChars(str, chrs)
  local s = str:gsub("["..chrs:gsub("%W","%%%1").."]", '')
  return s
end