
---------------
-- CONSTANTS --
---------------

-- NOTE: "DMI"s are the legacy name for quantities that DM tracks.
-- However, the term "quantity" refers to a quantity that is displayed.
-- There are more Quantities than there are DMIs.
-- Better names for these two things would be "Real" vs. "Virtual" or "Display" quantities.

DMI_DAMAGE = 1;
DMI_HEALING = 2;
DMI_DAMAGED = 3;
DMI_HEALED = 4;
	DMI_REPORT_MAX = 4;
DMI_CURING = 5;
DMI_OVERHEAL = 6;
DMI_ABSHEAL = 7;
DMI_BUILTIN_MAX = 7;
DMI_MAX = 7;

-- Just legacy crap.
DMI_1 = DMI_DAMAGE;
DMI_2 = DMI_HEALING;
DMI_3 = DMI_DAMAGED;
DMI_4 = DMI_HEALED;

-- Defined Quantities.
-- These numbers index into the DM_QUANTDEFS table.
DamageMeters_Quantity_DAMAGE = 1;
DamageMeters_Quantity_HEALING = 2;
DamageMeters_Quantity_DAMAGED = 3;
DamageMeters_Quantity_HEALINGRECEIVED = 4;
DamageMeters_Quantity_CURING = 5;
DamageMeters_Quantity_OVERHEAL = 6;
DamageMeters_Quantity_ABSHEAL = 7;

DamageMeters_Quantity_DPS = 8;
DamageMeters_Quantity_HPS = 9;
DamageMeters_Quantity_DTPS = 10;
DamageMeters_Quantity_HTPS = 11;
DamageMeters_Quantity_CURINGFIGHT = 12;
DamageMeters_Quantity_OVERHEALFIGHT = 13;
DamageMeters_Quantity_ABSHEALFIGHT = 14;

DamageMeters_Quantity_TIME = 15;
DamageMeters_Quantity_NETDAMAGE = 16;
DamageMeters_Quantity_NETHEALING = 17;
DamageMeters_Quantity_DAMAGEPLUSHEALING = 18;
DamageMeters_Quantity_HEALTH = 19;
DamageMeters_Quantity_OVERHEALPERCENTAGE = 20;

-- While this is assigned here, this number is actually CALCULATED when 
-- DamageMeters is loaded.  (It is set to the length of DM_QUANTDEFS).  This was intended to 
-- allow other mods to add quantities, but who knows if its really possible.
DamageMeters_Quantity_MAX = 18;

-- These aren't actualy quantities, but rather just flags to DoReport.
DamageMeters_ReportQuantity_Total = -1;
DamageMeters_ReportQuantity_Leaders = -2;
DamageMeters_ReportQuantity_Events = -3;

-------------------------------------------------------------------------------
-- Virtual Quantity Definitions --

--[[
	name			- The localized string name of this quantity.
	psName			- [Optional] The string name of the quantity when it is per-second.
	defaultColor	- The default color of the frame when the quantity is visible.
	dmi				- [Optional] The DMI that this quantity is based on.
	bUsesFightTable - True if this quantity uses the Fight table instead of the Active one.
	toggleQuant		- The quantity to switch to when we are toggled.  (Note that toggling really means between fight/regular).
	filterDefault	- Default quantity filter setting.
	pfnGetValue		- function(playerStruct).  A quantityDef can specify a function that the system is to call
						to get a given player's value for this quantity.
	titanAbbrev		- A short string (ideally a few characters) to be shown in the titan panel for this quantity.
]]--

DM_QUANTDEFS = {};

DM_QUANTDEFS[DamageMeters_Quantity_DAMAGE] = {
	name = DM_QUANTSTRING_DAMAGEDONE,
	psName = nil,
	defaultColor = {1.0, 0.0, 0.0, 0.8},
	dmi = DMI_DAMAGE,
	bUsesFightTable = false,
	toggleQuant = DamageMeters_Quantity_DPS,
	filterDefault = true,
	titanAbbrev = DM_QUANTABBREV_DAMAGEDONE;
};
DM_QUANTDEFS[DamageMeters_Quantity_HEALING] = {
	name = DM_QUANTSTRING_HEALINGDONE,
	psName = nil,
	defaultColor = {0.0, 1.0, 0.0, 0.8},
	dmi = DMI_HEALING,
	bUsesFightTable = false,
	toggleQuant = DamageMeters_Quantity_HPS,
	filterDefault = true,
	titanAbbrev = DM_QUANTABBREV_HEALINGDONE;
};
DM_QUANTDEFS[DamageMeters_Quantity_DAMAGED] = {
	name = DM_QUANTSTRING_DAMAGETAKEN,
	psName = nil,
	defaultColor = {1.0, 0.5, 0.0, 0.8},
	dmi = DMI_DAMAGED,
	bUsesFightTable = false,
	toggleQuant = DamageMeters_Quantity_DTPS,
	filterDefault = true,
	titanAbbrev = DM_QUANTABBREV_DAMAGETAKEN;
};
DM_QUANTDEFS[DamageMeters_Quantity_HEALINGRECEIVED] = {
	name = DM_QUANTSTRING_HEALINGTAKEN,
	psName = nil,
	defaultColor = {0.0, 0.0, 1.0, 0.8},
	dmi = DMI_HEALED,
	bUsesFightTable = false,
	toggleQuant = DamageMeters_Quantity_HTPS,
	filterDefault = true,
	titanAbbrev = DM_QUANTABBREV_HEALINGTAKEN;
};
DM_QUANTDEFS[DamageMeters_Quantity_CURING] = {
	name = DM_QUANTSTRING_CURING,
	psName = nil,
	defaultColor = {0.6, 0.7, 0.8, 0.8},
	dmi = DMI_CURING,
	bUsesFightTable = false,
	toggleQuant = DamageMeters_Quantity_CURINGFIGHT,
	filterDefault = true,
	titanAbbrev = DM_QUANTABBREV_CURING;
};
DM_QUANTDEFS[DamageMeters_Quantity_OVERHEAL] = {
	name = DM_QUANTSTRING_OVERHEAL,
	psName = nil,
	defaultColor = {1.0, 1.0, 0.0, 0.8},
	dmi = DMI_OVERHEAL,
	bUsesFightTable = false,
	filterDefault = true,
	toggleQuant = DamageMeters_Quantity_OVERHEALFIGHT,
	titanAbbrev = DM_QUANTABBREV_OVERHEAL;
};
DM_QUANTDEFS[DamageMeters_Quantity_ABSHEAL] = {
	name = DM_QUANTSTRING_ABSHEAL,
	psName = nil,
	defaultColor = {1.0, 0.0, 1.0, 0.8}, 
	dmi = DMI_ABSHEAL,
	bUsesFightTable = false,
	filterDefault = false,
	toggleQuant = DamageMeters_Quantity_ABSHEALFIGHT,
	titanAbbrev = DM_QUANTABBREV_ABSHEAL;
};

DM_QUANTDEFS[DamageMeters_Quantity_DPS] = {
	name = DM_QUANTSTRING_DAMAGEDONE_FIGHT,
	psName = DM_QUANTSTRING_DAMAGEDONE_PS,
	defaultColor = {0.8, 0.0, 0.0, 0.8},
	dmi = DMI_DAMAGE,
	bUsesFightTable = true,
	toggleQuant = DamageMeters_Quantity_DAMAGE,
	filterDefault = true,
	titanAbbrev = DM_QUANTABBREV_DAMAGEDONE_PS;
};
DM_QUANTDEFS[DamageMeters_Quantity_HPS] = {
	name = DM_QUANTSTRING_HEALINGDONE_FIGHT,
	psName = DM_QUANTSTRING_HEALINGDONE_PS,
	defaultColor = {0.0, 0.8, 0.0, 0.8},
	dmi = DMI_HEALING,
	bUsesFightTable = true,
	toggleQuant = DamageMeters_Quantity_HEALING,
	filterDefault = true,
	titanAbbrev = DM_QUANTABBREV_HEALINGDONE_PS;
};
DM_QUANTDEFS[DamageMeters_Quantity_DTPS] = {
	name = DM_QUANTSTRING_DAMAGETAKEN_FIGHT,
	psName = DM_QUANTSTRING_DAMAGETAKEN_PS,
	defaultColor = {1.0, 0.7, 0.2, 0.8},
	dmi = DMI_DAMAGED,
	bUsesFightTable = true,
	toggleQuant = DamageMeters_Quantity_DAMAGED,
	filterDefault = true,
	titanAbbrev = DM_QUANTABBREV_DAMAGETAKEN_PS,
};
DM_QUANTDEFS[DamageMeters_Quantity_HTPS] = {
	name = DM_QUANTSTRING_HEALINGTAKEN_FIGHT,
	psName = DM_QUANTSTRING_HEALINGTAKEN_PS,
	defaultColor = {0.0, 0.0, 0.8, 0.8},
	dmi = DMI_HEALED,
	bUsesFightTable = true,
	toggleQuant = DamageMeters_Quantity_HEALINGRECEIVED,
	filterDefault = true,
	titanAbbrev = DM_QUANTABBREV_HEALINGTAKEN_PS,
};
DM_QUANTDEFS[DamageMeters_Quantity_CURINGFIGHT] = {
	name = DM_QUANTSTRING_CURING_FIGHT,
	psName = DM_QUANTSTRING_CURING_PS,
	defaultColor = {0.4, 0.5, 0.6, 0.8},
	dmi = DMI_CURING,
	bUsesFightTable = true,
	toggleQuant = DamageMeters_Quantity_CURING,
	filterDefault = true,
	titanAbbrev = DM_QUANTABBREV_CURING_PS,
};
DM_QUANTDEFS[DamageMeters_Quantity_OVERHEALFIGHT] = {
	name = DM_QUANTSTRING_OVERHEAL_FIGHT,
	psName = DM_QUANTSTRING_OVERHEAL_PS,
	defaultColor = {0.4, 0.4, 0.0, 0.8},
	dmi = DMI_OVERHEAL,
	bUsesFightTable = true,
	filterDefault = true,
	toggleQuant = DamageMeters_Quantity_OVERHEALPERCENTAGE,
	titanAbbrev = DM_QUANTABBREV_OVERHEAL_PS,
};
DM_QUANTDEFS[DamageMeters_Quantity_ABSHEALFIGHT] = {
	name = DM_QUANTSTRING_ABSHEAL_FIGHT,
	psName = DM_QUANTSTRING_ABSHEAL_PS,
	defaultColor = {0.8, 0.0, 0.8, 0.8}, 
	dmi = DMI_ABSHEAL,
	bUsesFightTable = true,
	filterDefault = false,
	toggleQuant = DamageMeters_Quantity_ABSHEAL,
	titanAbbrev = DM_QUANTABBREV_ABSHEAL_PS,
};

DM_QUANTDEFS[DamageMeters_Quantity_TIME] = {
	name = DM_QUANTSTRING_IDLETIME,
	psName = nil,
	defaultColor = {0.0, 0.4, 1.0, 0.8},
	dmi = nil,
	bUsesFightTable = false,
	filterDefault = true,
	pfnGetValue = function(playerStruct) return GetTime() - playerStruct.lastTime; end,
	formatString = "%.1f",
	titanAbbrev = DM_QUANTABBREV_IDLETIME;
};

DM_QUANTDEFS[DamageMeters_Quantity_NETDAMAGE] = {
	name = DM_QUANTSTRING_NETDAMAGE,
	psName = nil,
	defaultColor = {0.5, 0.0, 0.0, 0.8},
	dmi = nil,
	bUsesFightTable = false,
	filterDefault = false,
	pfnGetValue = function(playerStruct) return playerStruct.dmiData[DMI_DAMAGE].q - playerStruct.dmiData[DMI_DAMAGED].q; end,
	titanAbbrev = DM_QUANTABBREV_NETDAMAGE;
};
DM_QUANTDEFS[DamageMeters_Quantity_NETHEALING] = {
	name = DM_QUANTSTRING_NETHEALING,
	psName = nil,
	defaultColor = {0.0, 0.5, 0.0, 0.8},
	dmi = nil,
	bUsesFightTable = false,
	filterDefault = false,
	pfnGetValue = function(playerStruct) return playerStruct.dmiData[DMI_HEALING].q - playerStruct.dmiData[DMI_HEALED].q; end,
	titanAbbrev = DM_QUANTABBREV_NETHEALING;
};
DM_QUANTDEFS[DamageMeters_Quantity_DAMAGEPLUSHEALING] = {
	name = DM_QUANTSTRING_DAMAGEPLUSHEALING,
	psName = nil,
	defaultColor = {0.5, 0.5, 0.5, 0.8},
	dmi = nil,
	bUsesFightTable = false,
	filterDefault = false,
	pfnGetValue = function(playerStruct) return playerStruct.dmiData[DMI_DAMAGE].q + playerStruct.dmiData[DMI_HEALING].q; end,
	titanAbbrev = DM_QUANTABBREV_DAMAGEPLUSHEALING;
};
DM_QUANTDEFS[DamageMeters_Quantity_HEALTH] = {
	name = DM_QUANTSTRING_HEALTH;
	psName = nil,
	defaultColor = {0.5, 0.3, 0.7, 0.8},
	dmi = nil,
	bUsesFightTable = false,
	filterDefault = false,
	pfnGetValue = function(playerStruct) return playerStruct.health; end;
	titanAbbrev = DM_QUANTABBREV_HEALTH;
};
DM_QUANTDEFS[DamageMeters_Quantity_OVERHEALPERCENTAGE] = {
	name = DM_QUANTSTRING_OVERHEAL_PERCENTAGE,
	psName = nil,
	defaultColor = {0.5, 0.5, 0.7, 0.8},
	dmi = nil,
	bUsesFightTable = false,
	filterDefault = false,
	pfnGetValue = function(playerStruct) return floor(100 * DM_GetFraction(playerStruct.dmiData[DMI_OVERHEAL].q, playerStruct.dmiData[DMI_HEALING].q + playerStruct.dmiData[DMI_OVERHEAL].q)); end,
	toggleQuant = DamageMeters_Quantity_OVERHEAL,
	titanAbbrev = DM_QUANTABBREV_OVERHEAL_PERCENTAGE;
};
--[[
DM_QUANTDEFS[DamageMeters_Quantity_DANDERATING] = {
	name = DM_QUANTSTRING_DANDERATING,
	psName = nil,
	defaultColor = {1.0, 0.6, 0.6, 0.8},
	dmi = nil,
	bUsesFightTable = false,
	filterDefault = false,
	pfnGetValue = 
		function(playerStruct) 
			local dmgIndex = (	DamageMeters_totalsTable[DMT_ACTIVE] ~= nil and 
								DamageMeters_totalsTable[DMT_ACTIVE][DMI_DAMAGE] ~= nil and 
								DamageMeters_totalsTable[DMT_ACTIVE][DMI_DAMAGE] > 0) 
									and 
										floor(playerStruct.dmiData[DMI_DAMAGE].q * 500.0 / DamageMeters_totalsTable[DMT_ACTIVE][DMI_DAMAGE]) 
									or 
										0;
			local healIndex = (	DamageMeters_totalsTable[DMT_ACTIVE] ~= nil and 
								DamageMeters_totalsTable[DMT_ACTIVE][DMI_HEALING] ~= nil and
								DamageMeters_totalsTable[DMT_ACTIVE][DMI_HEALING] > 0) 
									and 
										floor(playerStruct.dmiData[DMI_HEALING].q * 500.0 / DamageMeters_totalsTable[DMT_ACTIVE][DMI_HEALING]) 
									or 
										0;
			return dmgIndex + healIndex;		
		end,
};
]]--

-------------
-- GLOBALS -- 
-------------
DamageMeters_rankTables = {};
DamageMeters_rankTables[DM_TABLE_A] = {};
DamageMeters_rankTables[DM_TABLE_B] = {};
DamageMeters_rankTables[DMT_FIGHT] = {};

DamageMeters_totalsTable = {};
DamageMeters_totalsTable[DM_TABLE_A] = {};
DamageMeters_totalsTable[DM_TABLE_B] = {};
DamageMeters_totalsTable[DMT_FIGHT] = {};

-------------------------------------------------
-- FUNCTIONS --
-------------------------------------------------

function DamageMeters_IsQuantityFight(quantity)
	if (DM_QUANTDEFS[quantity]) then
		return DM_QUANTDEFS[quantity].bUsesFightTable;
	end

	-- DMReporter can call this with plug-in quantities.  In those cases the quantdefs aren't filled in.
	-- This could be a problem if a plugin uses a quantity that isn't just a simple DMI.
	return false;
end

function DamageMeters_IsQuantityPS(quantity, forcePSFlag)
	return (DamageMeters_flags[DMFLAG_showFightAsPS] or forcePSFlag) and (DM_QUANTDEFS[quantity].bUsesFightTable);
end

function DamageMeters_GetQuantityDMI(quantity)
	if (nil == quantity) then
		DMPrintD("DamageMeters_GetQuantityDMI: quantity = nil");
	end

	return DM_QUANTDEFS[quantity].dmi;
end

-- This function is about as fast as I know how to make it.  It doesn't protect
-- against invalid inputs at all.
function DamageMeters_GetQuantityValue(quantity, tableIndex, playerIndex)
	local playerStruct = DamageMeters_tables[tableIndex][playerIndex];

	if (DamageMeters_Quantity_TIME == DamageMeters_quantity) then
		return GetTime() - playerStruct.lastTime;
	else
		local quantDef = DM_QUANTDEFS[quantity];

		local value = 0;
		if (nil ~= playerStruct) then
			if (nil ~= quantDef.pfnGetValue) then
				value = quantDef.pfnGetValue(playerStruct);
			else
				value = playerStruct.dmiData[quantDef.dmi].q;
			end

			if (DamageMeters_IsQuantityPS(quantity)) then
				value = DamageMeters_GetCombatValuePS(value);
			end
		end

		return value;
	end
end

-- This is a slow function.  Not used much, I think.
function DamageMeters_GetQuantityValueString(quantity, playerName)

	local quantDef = DM_QUANTDEFS[quantity];
	local tableIndex;
	if (quantDef.bUsesFightTable) then
		tableIndex = DMT_FIGHT;
	else
		tableIndex = DMT_ACTIVE;
	end

	local playerIndex = DamageMeters_GetPlayerIndex(playerName, tableIndex);
	if (playerIndex ~= nil) then
		local value = DamageMeters_GetQuantityValue(quantity, tableIndex, playerIndex);
		
		local formatString;
		if (quantDef.formatString) then
			formatString = quantDef.formatString;
		else
			if (DamageMeters_IsQuantityPS(quantity)) then
				formatString = "%0.1f";
			else
				formatString = "%d";
			end
		end

		return string.format(formatString, value);
	else
		return "-";
	end
end

function DamageMeters_GetQuantityString(quantity, forcePSFlag)
	if (DamageMeters_IsQuantityPS(quantity, forcePSFlag)) then
		return DM_QUANTDEFS[quantity].psName;
	end

	return DM_QUANTDEFS[quantity].name;
end

function DamageMeters_GetPlayerIndex(player, searchTableIndex)
	if (nil == searchTableIndex) then
		searchTableIndex = DMT_ACTIVE;
	end

	local i;
	for i = 1, table.getn(DamageMeters_tables[searchTableIndex]) do
		if (DamageMeters_tables[searchTableIndex][i].player == player) then
			return i;
		end
	end
	
	return nil;
end

DMSortTemp_sortType = 1;
DMSortTemp_quantity = 1;
DMSortTemp_dmi = 1;

-- This could be broken into two separate increasing/decreasing functions
-- for increased performance.
function DMSortFunc_Default(a,b) 	
	local result;
	if (a.dmiData[DMSortTemp_dmi].q == b.dmiData[DMSortTemp_dmi].q) then
		if (DMSortTemp_sortType == DamageMeters_Sort_INCREASING) then
			result = a.player > b.player;
		else
			result = a.player < b.player;
		end
	else
		if (DMSortTemp_sortType == DamageMeters_Sort_INCREASING) then
			result = a.dmiData[DMSortTemp_dmi].q < b.dmiData[DMSortTemp_dmi].q;
		else
			result = a.dmiData[DMSortTemp_dmi].q > b.dmiData[DMSortTemp_dmi].q;
		end
	end

	return result;
end

function DMSortFunc_Alphabetical(a, b)
	return a.player < b.player;
end

function DMSortFunc_Time(a, b)
	local result;
	if (a.lastTime == b.lastTime) then
		result = a.player < b.player;
	else
		result = a.lastTime < b.lastTime;
	end

	if (DMSortTemp_sortType == DamageMeters_Sort_INCREASING) then
		result = not result;
	end
	return result;
end

function DMSortFunc_Custom(a, b)
	local valfunc = DM_QUANTDEFS[DMSortTemp_quantity].pfnGetValue;
	local aValue = valfunc(a);
	local bValue = valfunc(b);
	local result;
	if (aValue == bValue) then
		if (DMSortTemp_sortType == DamageMeters_Sort_INCREASING) then
			result = a.player > b.player;
		else
			result = a.player < b.player;
		end
	else
		if (DMSortTemp_sortType == DamageMeters_Sort_INCREASING) then
			result = aValue < bValue;
		else
			result = aValue > bValue;
		end
	end

	return result;
end

-- In the case of a tie, sort alphabetically.
function DamageMeters_DoSort(tableToSort, quantity, sortType)
	if (DM_Bypass["Sort"] == true) then
		return;
	end

	if (nil == sortType) then
		sortType = DamageMeters_sort;
	end
	DMSortTemp_sortType = sortType;
	DMSortTemp_quantity = quantity;
	DMSortTemp_dmi = DM_QUANTDEFS[quantity].dmi;

	if (DamageMeters_Sort_ALPHABETICAL == sortType) then
		table.sort(tableToSort, DMSortFunc_Alphabetical);
		return;
	end

	if (quantity == DamageMeters_Quantity_TIME) then
		table.sort(tableToSort, DMSortFunc_Time);
		return;
	elseif (DM_QUANTDEFS[quantity].pfnGetValue ~= nil) then
		table.sort(tableToSort, DMSortFunc_Custom);
		return;
	else
		DMSortTemp_quantity = DamageMeters_GetQuantityDMI(quantity);
		table.sort(tableToSort, DMSortFunc_Default);
		return;
	end
end

-- No reason to believe this doesn't work but it has never been used.
--[[
function DamageMeters_BuildSortHash(sortType)
	DamageMeters_tableSortHash = {};

	-- Build the unsorted hash.
	local quant;
	for quant = 1, DamageMeters_Quantity_MAX do
		DamageMeters_tableSortHash[quant] = {};

		local quantTable = (DM_QUANTDEFS[quant].bUsesFightTable) and DMT_FIGHT or DMT_ACTIVE;

		-- Populate the table for this quantity.
		local playerIndex;
		for playerIndex = 1, table.getn(DamageMeters_tables[quantTable]) do
			DamageMeters_tableSortHash[quant][playerIndex] = {};
			DamageMeters_tableSortHash[quant][playerIndex].playerName = DamageMeters_tables[quantTable][playerIndex].player;
			DamageMeters_tableSortHash[quant][playerIndex].playerIndex = playerIndex;
			DamageMeters_tableSortHash[quant][playerIndex].value = DamageMeters_GetQuantityValue(quant, quantTable, playerIndex);
		end

		-- Sort this quantity.
		table.sort(DamageMeters_tableSortHash[quant],
			function(a,b)
				if (a.value == b.value or DamageMeters_Sort_ALPHABETICAL == sortType) then
					return a.playerName < b.playerName;
				elseif (DamageMeters_Sort_INCREASING == sortType) then
					return a.value > b.value;
				else -- DamageMeters_Sort_DECREASING implied.
					return a.value < b.value;
				end
			end	
			);
	end
end
]]--


DM_fakeTables = {};
DM_fakeTablesInitialized = false;

function DamageMeters_InitFakeTables()	
	for ix = 1, 2 do
		DM_fakeTables[ix] = {};
		for index = 1, DamageMeters_TABLE_MAX do
			DM_fakeTables[ix][index] = {};
			DM_fakeTables[ix][index].player = nil;
			DM_fakeTables[ix][index].lastTime = -1;
			DM_fakeTables[ix][index].dmiData = {};
			for dmi = 1, DMI_MAX do
				DM_fakeTables[ix][index].dmiData[dmi] = {};
				DM_fakeTables[ix][index].dmiData[dmi].q = 0;
			end
		end
	end

	DM_fakeTablesInitialized = true;
end


-- The bRankQuantities parameter is a hack.  Its only used by TitanDamageMeters.lua.
-- It will only work as expected if the tableIx isn't DMT_FIGHT already.
-- If true, this function will determine the ranks of -quantities-, not DMIs.
function DamageMeters_DetermineRanks(tableIx, bRankQuantities)
	if (DM_Bypass["Determine Ranks"] == true) then
		return;
	end

	if (not DM_fakeTablesInitialized) then
		DamageMeters_InitFakeTables();
	end

	local index, playerStruct;
	local indexMax = table.getn(DamageMeters_tables[tableIx]);
	for index = 1, indexMax do
		if (nil == DM_fakeTables[1][index]) then
			DM_fakeTables[1][index] = {};
			DM_fakeTables[1][index].dmiData = {};
			for dmi = 1, DMI_MAX do
				DM_fakeTables[1][index].dmiData[dmi] = {};
			end
		end

		DM_fakeTables[1][index].player = DamageMeters_tables[tableIx][index].player;
		DM_fakeTables[1][index].lastTime = DamageMeters_tables[tableIx][index].lastTime;
		local dmi;
		for dmi = 1, DMI_MAX do
			DM_fakeTables[1][index].dmiData[dmi].q = DamageMeters_tables[tableIx][index].dmiData[dmi].q;
		end
	end
	for index = (indexMax + 1), DamageMeters_TABLE_MAX do
		DM_fakeTables[1][index] = nil;
	end

	if (bRankQuantities) then
		local indexMax = table.getn(DamageMeters_tables[DMT_FIGHT]);
		for index = 1, indexMax do
			if (nil == DM_fakeTables[2][index]) then
				DM_fakeTables[2][index] = {};
				DM_fakeTables[2][index].dmiData = {};
				for dmi = 1, DMI_MAX do
					DM_fakeTables[2][index].dmiData[dmi] = {};
				end
			end

			DM_fakeTables[2][index].player = DamageMeters_tables[DMT_FIGHT][index].player;
			DM_fakeTables[2][index].lastTime = DamageMeters_tables[DMT_FIGHT][index].lastTime;
			local dmi;
			for dmi = 1, DMI_MAX do
				DM_fakeTables[2][index].dmiData[dmi].q = DamageMeters_tables[DMT_FIGHT][index].dmiData[dmi].q;
			end
		end
		for index = (indexMax + 1), DamageMeters_TABLE_MAX do
			DM_fakeTables[2][index] = nil;
		end
	end

	-----------
	if (DM_Bypass["Determine Ranks 1"] == true) then
		return;
	end

	local count = table.getn(DamageMeters_tables[tableIx]);
	local countPS = table.getn(DamageMeters_tables[DMT_FIGHT]);

	local quantity;
	local max = bRankQuantities and DamageMeters_Quantity_MAX or DMI_MAX;
	for quantity = 1, max do
		local fakeTableToUseIndex;
		local countToUse;
		local tableIxToUse;
		if (bRankQuantities and DamageMeters_IsQuantityPS(quantity)) then
			fakeTableToUseIndex = 2;
			countToUse = countPS;
			tableIxToUse = DMT_FIGHT;
		else
			fakeTableToUseIndex = 1;
			countToUse = count;
			tableIxToUse = tableIx;
		end

		DamageMeters_DoSort(DM_fakeTables[fakeTableToUseIndex], quantity, DamageMeters_Sort_DECREASING);

		-- Mark all players as unused in the rank table.  We'll mark them used as we
		-- need to in the next loop.
		for playerName, struct in DamageMeters_rankTables[tableIxToUse] do
			struct._used = false;
		end

		local index;
		for index = 1, countToUse do
			local playerName = DM_fakeTables[fakeTableToUseIndex][index].player;
			if (DamageMeters_rankTables[tableIxToUse][playerName] == nil) then
				DamageMeters_rankTables[tableIxToUse][playerName] = {};
			end	
			DamageMeters_rankTables[tableIxToUse][playerName][quantity] = index;
			DamageMeters_rankTables[tableIxToUse][playerName]._used = true;
		end

		-- Remove any players from the rank table we aren't using.
		for playerName, struct in DamageMeters_rankTables[tableIxToUse] do
			if (struct._used == false) then
				DamageMeters_rankTables[tableIxToUse][playerName] = nil;
			end
		end
	end
end

function DamageMeters_DetermineTotals()
	--DamageMeters_totalsTable = {};
	DamageMeters_DetermineTotalsForATable(DMT_ACTIVE);	
	DamageMeters_DetermineTotalsForATable(DMT_FIGHT);	
end

function DamageMeters_DetermineTotalsForATable(tableIx)
	--DamageMeters_totalsTable[tableIx] = {};
	local dmi;
	for dmi = 1, DMI_MAX do
		DamageMeters_totalsTable[tableIx][dmi] = 0;
	end

	local index, info;
	for index,info in DamageMeters_tables[tableIx] do 
		for dmi = 1, DMI_MAX do
			DamageMeters_totalsTable[tableIx][dmi] = DamageMeters_totalsTable[tableIx][dmi] + info.dmiData[dmi].q;
		end
	end
end

function DamageMeters_DoReport(reportQuantity, destination, invert, start, count, tellTarget)
	local msg;

	if (destination == "BUFFER") then
		DamageMeters_reportBuffer = "";
	elseif (destination == "PARTY") then
		if (GetNumPartyMembers() == 0) then
			DMPrint(DM_ERROR_NOPARTY);
			return;
		end
	elseif (destination == "RAID") then
		if (GetNumRaidMembers() == 0) then
			DMPrint(DM_ERROR_NORAID);
			return;
		end
	end

	local tableIx = DMT_ACTIVE;
	if (DamageMeters_IsQuantityFight(reportQuantity)) then
		tableIx = DMT_FIGHT;
	end

	-- Determine bounds.
	local finish = start + count - 1;
	if (finish > table.getn(DamageMeters_tables[tableIx])) then
		finish = table.getn(DamageMeters_tables[tableIx]);
	end

	local step = 1;
	if (invert) then
		start = finish;
		finish = 1;
		step = -1;
	end

	------------
	-- Header --
	------------
	if (reportQuantity == DamageMeters_ReportQuantity_Total) then
		msg = string.format(DM_MSG_FULLREPORTHEADER1, count, table.getn(DamageMeters_tables[tableIx]));
		DamageMeters_SendReportMsg(msg, destination, tellTarget);
		DamageMeters_SendReportMsg(DM_MSG_FULLREPORTHEADER2, destination, tellTarget);
		msg = DM_MSG_FULLREPORTHEADER3;
	elseif (reportQuantity == DamageMeters_ReportQuantity_Leaders) then
		local header = string.format(DM_MSG_LEADERREPORTHEADER, count, table.getn(DamageMeters_tables[tableIx]));
		local q;
		for q = 1, DMI_REPORT_MAX do
			header = string.format("%s| %-21s", header, DM_QUANTDEFS[q].name);
		end
		msg = header.."\n-------------------------------------------------------------------------------------------";
	elseif (reportQuantity == DamageMeters_ReportQuantity_Events) then
		msg = string.format(DM_MSG_EVENTREPORTHEADER, count, table.getn(DamageMeters_tables[tableIx]));
	else
		msg = string.format(DM_MSG_REPORTHEADER, DamageMeters_GetQuantityString(reportQuantity), count, table.getn(DamageMeters_tables[tableIx]));
	end
	DamageMeters_SendReportMsg(msg, destination, tellTarget);

	if (reportQuantity > 0) then
		DamageMeters_DoSort(DamageMeters_tables[tableIx], reportQuantity);
	end

	local reportQuantityDMI = nil;
	if (reportQuantity > 0) then
		reportQuantityDMI = DamageMeters_GetQuantityDMI(reportQuantity);
	end

	-- Calculate totals.
	local totalValue = 0;
	local index;
	local info;
	local totals = {0, 0, 0, 0, 0, 0, 0};
	if (reportQuantity > 0) then
		if (DamageMeters_Quantity_TIME ~= reportQuantity) then
			local playerIndex;
			for playerIndex = 1, table.getn(DamageMeters_tables[tableIx]) do 
				totalValue = totalValue + DamageMeters_GetQuantityValue(reportQuantity, tableIx, playerIndex);
			end
		end
	elseif (reportQuantity == DamageMeters_ReportQuantity_Total or
			reportQuantity == DamageMeters_ReportQuantity_Leaders) then
		for index,info in DamageMeters_tables[tableIx] do 
			totals[1] = totals[1] + info.dmiData[DMI_1].q;
			totals[2] = totals[2] + info.dmiData[DMI_2].q;
			totals[3] = totals[3] + info.dmiData[DMI_3].q;
			totals[4] = totals[4] + info.dmiData[DMI_4].q;
			totals[5] = totals[5] + info.dmiData[DMI_DAMAGE].hitCount;
			totals[6] = totals[6] + info.dmiData[DMI_DAMAGE].critCount;
		end

		DamageMeters_DetermineRanks(tableIx);
	end


	---------------
	-- Main Loop --
	---------------
	local formatStrTotalMain_A = "%-12s %7d[%2d] %7d[%2d] %7d[%2d] %7d[%2d] %7d %7d";
	local formatStrTotalTotals = "%-12s %11d %11d %11d %11d %7d %7d";
	-- Careful here--if there isn't a space after each | it can lock up.
	local formatStrTotalMain_B = "%2d| %8d %-12s| %8d %-12s| %8d %-12s| %8d %-12s";
	local formatStrLeaderTotals= " =| %-20d | %-20d | %-20d | %-20d";
	local currentTime = GetTime();
	local i;
	local showThis;
	local visibleTotal = 0;
	--DMPrintD("start = "..start..", finish = "..finish..", quantity = "..reportQuantity);
	if (count > 0) then
		for i = start, finish, step do
			local value;
			msg = "";

			if (reportQuantity == DamageMeters_ReportQuantity_Total) then
				msg = string.format(formatStrTotalMain_A, DamageMeters_tables[tableIx][i].player, 
						DamageMeters_tables[tableIx][i].dmiData[DMI_1].q, DamageMeters_rankTables[tableIx][DamageMeters_tables[tableIx][i].player][1], 
						DamageMeters_tables[tableIx][i].dmiData[DMI_2].q, DamageMeters_rankTables[tableIx][DamageMeters_tables[tableIx][i].player][2], 
						DamageMeters_tables[tableIx][i].dmiData[DMI_3].q, DamageMeters_rankTables[tableIx][DamageMeters_tables[tableIx][i].player][3], 
						DamageMeters_tables[tableIx][i].dmiData[DMI_4].q, DamageMeters_rankTables[tableIx][DamageMeters_tables[tableIx][i].player][4], 
						DamageMeters_tables[tableIx][i].dmiData[DMI_DAMAGE].hitCount, DamageMeters_tables[tableIx][i].dmiData[DMI_DAMAGE].critCount);
			elseif(reportQuantity == DamageMeters_ReportQuantity_Leaders) then
				local leaders = {};
				local leaderIndexes = {};
				local qIx;
				for qIx = 2, DMI_MAX do
					local leaderName, rank;
					for leaderName, rank in DamageMeters_rankTables[tableIx] do
						--DMPrint(i.." "..leaderName.." "..rank[qIx]);
						if (rank[qIx] == i) then
							leaders[qIx] = leaderName;
							leaderIndexes[qIx] = DamageMeters_GetPlayerIndex(leaderName);
							break;
						end
					end
				end

				msg = string.format(formatStrTotalMain_B, i, 
						DamageMeters_tables[tableIx][i].dmiData[DMI_1].q, DamageMeters_tables[tableIx][i].player,	
						DamageMeters_tables[tableIx][leaderIndexes[2]].dmiData[DMI_2].q, leaders[2], 
						DamageMeters_tables[tableIx][leaderIndexes[3]].dmiData[DMI_3].q, leaders[3], 
						DamageMeters_tables[tableIx][leaderIndexes[4]].dmiData[DMI_4].q, leaders[4]); 
			elseif (reportQuantity == DamageMeters_ReportQuantity_Events) then
				DamageMeters_DumpPlayerEvents(DamageMeters_tables[tableIx][i].player, destination, true, i, totals);
			else
				if (DamageMeters_Quantity_TIME == reportQuantity) then
					local idleTime = currentTime - DamageMeters_tables[tableIx][i].lastTime;
					msg = string.format("#%.2d: %-16s  %d:%.02d", i, DamageMeters_tables[tableIx][i].player, idleTime / 60, math.mod(idleTime, 60));
				else
					local value = DamageMeters_GetQuantityValue(reportQuantity, tableIx, i);
					-- 4/2/06: Removed this check.  Was never necessary, and now that some quantitites can be
					-- negative its detrimental.
					--if (value > 0) then
						visibleTotal = visibleTotal + value;
						if (DamageMeters_IsQuantityPS(reportQuantity)) then
							msg = string.format("#%.2d: %-16s  %.1f", i, DamageMeters_tables[tableIx][i].player, value);
						else
							local percentage = (totalValue > 0) and (100 * value / totalValue) or 0;
							msg = string.format("#%.2d:  %.2f%%  %-16s  %d", i, percentage, DamageMeters_tables[tableIx][i].player, value);
						end
					--end
				end
			end
			
			if (msg ~= "") then
				DamageMeters_SendReportMsg(msg, destination, tellTarget);
			end
		end
	end

	------------
	-- Totals --
	------------
	if (reportQuantity == DamageMeters_ReportQuantity_Total or
		reportQuantity == DamageMeters_ReportQuantity_Leaders) then

		if (reportQuantity == DamageMeters_ReportQuantity_Total) then
			msg = string.format(formatStrTotalTotals, DM_MSG_TOTAL, totals[1], totals[2], totals[3], totals[4], totals[5], totals[6]);
		else
			msg = string.format(formatStrLeaderTotals, totals[1], totals[2], totals[3], totals[4], totals[5], totals[6]);
		end
		DamageMeters_SendReportMsg(msg, destination, tellTarget);

		-- Print a list of contributors.
		if (DamageMeters_flags[DMFLAG_haveContributors]) then
			msg = string.format(DM_MSG_COLLECTORS, UnitName("Player"));
			for contrib, unused in DamageMeters_contributorList do
				msg = msg..", "..contrib;
			end
			DamageMeters_SendReportMsg(msg, destination, tellTarget);
		end
	elseif (DamageMeters_Quantity_DPS == reportQuantity) then
		msg = string.format(DM_MSG_COMBATDURATION, DamageMeters_combatEndTime - DamageMeters_combatStartTime);
		DamageMeters_SendReportMsg(msg, destination, tellTarget);
	end

	if (reportQuantity > 0) then
		if (DamageMeters_Quantity_TIME == reportQuantity) then
			-- No total.
		elseif (DamageMeters_Quantity_DPS == reportQuantity) then
			msg = string.format(DM_MSG_REPORTTOTALDPS, totalValue, visibleTotal);
			DamageMeters_SendReportMsg(msg, destination, tellTarget);
		else
			msg = string.format(DM_MSG_REPORTTOTAL, totalValue, visibleTotal);
			DamageMeters_SendReportMsg(msg, destination, tellTarget);
		end
	end
end

function DamageMeters_DumpPlayerEvents(player, destination, bIncludeName, index, totals, singleQuantity)
	local tellTarget = nil;

	local playerIndex = DamageMeters_GetPlayerIndex(player, DMT_VISIBLE);
	--[[ removed when changed event system
	local playerEventStruct = DamageMeters_tables[DMT_VISIBLE][playerIndex].events;
	if (nil == playerEventStruct) then
		-- Makes spam.
		--DMPrintD("Player "..player.." not found.", nil, true);
		return;
	end
	]]--

	local prefix = "";
	if (bIncludeName) then
		if (index) then
			str = index..": "..player..":";
		else
			str = player..":";
		end
		prefix = "  ";
		DamageMeters_SendReportMsg(str, destination, tellTarget);
	end

	if (totals == nil) then
		totals = {};
		local ii;
		for ii = 1, table.getn(DamageMeters_tables[DMT_VISIBLE]) do
			local q;
			for q = 1, DMI_MAX do
				if (totals[q] == nil) then
					totals[q] = 0;
				end
				totals[q] = totals[q] + DamageMeters_tables[DMT_VISIBLE][ii].dmiData[q].q;
			end
		end
	end

	--[[ Remove firstMsg functionality.  Pounds the GC.
	if (DamageMeters_debugEnabled) then
		if (playerIndex and DamageMeters_tables[DMT_VISIBLE][playerIndex].firstMsg and DamageMeters_tables[DMT_VISIBLE][playerIndex].firstMsg["event"]) then
			local msg = "Last message: "..DamageMeters_tables[DMT_VISIBLE][playerIndex].firstMsg.event.." ("..DamageMeters_tables[DMT_VISIBLE][playerIndex].firstMsg.desc..")";
			DamageMeters_SendReportMsg(msg, destination, tellTarget);
			msg = "  "..DamageMeters_tables[DMT_VISIBLE][playerIndex].firstMsg.fullMsg.."\n";
			DamageMeters_SendReportMsg(msg, destination, tellTarget);
		end
	end
	]]--

	local headerDest = destination;
	if (headerDest == "TOOLTIP") then
		headerDest = "TOOLTIP_TITLE";
	end

	local dmi, eventStruct;
	if (singleQuantity) then
		dmi = DM_QUANTDEFS[singleQuantity].dmi;
		if (dmi) then
			eventStruct = DamageMeters_tables[DMT_VISIBLE][playerIndex].dmiData[dmi].events;
			if (eventStruct) then
				DamageMeters_DumpQuantityEvents(player, dmi, eventStruct, totals, headerDest, destination, tellTarget, prefix);
			end
		end
	else
		for dmi = 1, DMI_MAX do
			eventStruct = DamageMeters_tables[DMT_VISIBLE][playerIndex].dmiData[dmi].events;
			if (eventStruct) then
				DamageMeters_DumpQuantityEvents(player, dmi, eventStruct, totals, headerDest, destination, tellTarget, prefix);
			end
		end
	end

	DamageMeters_SendReportMsg("", destination, tellTarget);
end

function DamageMeters_DumpQuantityEvents(player, dmi, eventStruct, totals, headerDest, destination, tellTarget, prefix)
	if (eventStruct == nil) then
		return;
	end

	-- Calculate total.
	local total = 0;
	for spell, spellStruct in eventStruct.spellTable do
		-- Spells with "*" at the end are duplicates, and are not to be counted towards the total.
		if (string.sub(spell, -1) ~= "*") then
			total = total + spellStruct.value;
		end
	end
	local percentageOfTotal = (totals[dmi] > 0) and (100 * total / totals[dmi]) or (0);
	local rank = (DamageMeters_rankTables[DMT_ACTIVE][player] and DamageMeters_rankTables[DMT_ACTIVE][player][dmi]) and " #"..tostring(DamageMeters_rankTables[DMT_ACTIVE][player][dmi]) or "";
	str = string.format("%s = %s (%.1f%%)%s", prefix..DMI_NAMES[dmi], total, percentageOfTotal, rank);
	DamageMeters_SendReportMsg(str, headerDest, tellTarget);

	-- Cannot sort spells, as that table is indexed by string.
	if (eventStruct.dirty) then
		DamageMeters_BuildSpellHash(eventStruct);
	end

	-- Print
	for hashIndex = 1, table.getn(eventStruct.hash) do
		local spell = eventStruct.hash[hashIndex].spell;
		spellStruct = eventStruct.spellTable[spell];
		--DMPrint("Hash "..hashIndex.." = "..spell);

		local percentageOfTotal = (total > 0) and (100 * spellStruct.value / total) or (0);
		local critPercentage = (spellStruct.counts[DM_HIT] > 0) and (100 * spellStruct.counts[DM_CRT] / spellStruct.counts[DM_HIT]) or 0.0;
		local average = string.format("%.1f Avg", (spellStruct.counts[DM_HIT] and spellStruct.value / spellStruct.counts[DM_HIT] or 0));

		str = string.format(prefix.."  %s = %d (%.1f%%)  %d/%d %.1f%% %s", spell, spellStruct.value, percentageOfTotal, spellStruct.counts[DM_CRT], spellStruct.counts[DM_HIT], critPercentage, average);

		-- Show resistance info.
		if (dmi == DMI_DAMAGED and 
			(spellStruct.damageType <= DM_DMGTYPE_RESISTMAX or spellStruct.resistanceSum > 0)) then
			local avgResist = spellStruct.resistanceSum / spellStruct.resistanceCount;
			str = string.format("%s (%d %s)", str, avgResist, DM_DMGTYPENAMES[spellStruct.damageType]);
		end

		DamageMeters_SendReportMsg(str, destination, tellTarget);
	end
end

function DamageMeters_BuildSpellHash(eventStruct)
	if (DM_Bypass["BuildSpellHash"] == true) then
		return;
	end	

	if (nil == eventStruct.hash) then
		eventStruct.hash = {};
	end

	local hashIndex = 1;
	for spell, spellStruct in eventStruct.spellTable do
		eventStruct.hash[hashIndex] = {};
		eventStruct.hash[hashIndex].spell = spell;
		eventStruct.hash[hashIndex].value = spellStruct.value;
		hashIndex = hashIndex + 1;
	end

	table.sort(eventStruct.hash, 
		function(a,b) 
			return a.value > b.value;
		end
		);

	eventStruct.dirty = false;
end