--[[

	AddOn: DuelDatabase
	Description: An in-game duel database
	Author: Matthew Cox (matthew.a.cox@gmail.com)
	Copyright: Copyright 2005 by Matthew Cox
	Description: http://www.jroller.com/resources/pawnxing/WoWAddons.html
	Usage Notes: Feel free to use this how you like; however modification with redistribution is strictly prohibited.
	Release Notes: This version is backward-compatible with version 1.1.1.0

]]

--
-- Constants
--

Duelist_DEBUG = false;
Duelist_TEST = false;
Duelist_VERSION = "1.3"
Duelist_DUELFRAME_VERSION = "1.3";
Duelist_COLORTABLE = {r = 10;g = 100;b = 230;};

Duelist_SCORE_BASE = 1000;
Duelist_LEVEL_MULTIPLIER = .05;
Duelist_HEALTH_MULTIPLIER = .01;

Duelist_NO_GUILD = "ANameToLongToBeAGuildName";
Duelist_UNKNOWN = "Unknown Entity";

Duelist_RealmName = "";
Duelist_CurrentPlayer = "";

--
-- Tables Generators
--

function Duelist_newDuel(newName,newLevel,newClass,newRace,newFaction,newGuild,newHealth,newMaxHealth,
													newPLevel,newPGuild,newPHealth,newPMaxHealth,newLocation,newSub,newDuelStartTime)
	return {
		-- Opponent
		name = newName;
		level = newLevel;
	  class = newClass;
		race = newRace;
		faction = newFaction;
		guild = newGuild;
		health = newHealth;
		maxHealth = newMaxHealth;
	
		-- Player				
		pLevel = newPLevel;
		pGuild = newPGuild;
		pHealth = newPHealth;
		pMaxHealth = newPMaxHealth;
	
		-- Location
		location = newLocation;
		sub = newSub;
		
		-- Time
		duelStartTime = newDuelStartTime;
		duelEndTime;
		
		-- Results
		finished = false;
		won;
		percentTarget;
		percentPlayer;
		
		-- Scoring
		score; 
		levelDiff;
		levelMulti; 
		healthDiff;
		healthMulti;		
	}
end

function Duelist_newFaction(factionName) 
	return {
		name = factionName;
		guilds = {};
		fightsWon = 0;
		fightsLost = 0;				
	}
end

function Duelist_newGuild(guildName)
	return {
		name = guildName;
		opponents = {};
		fightsWon = 0;
		fightsLost = 0;
	}
end

function Duelist_newOpponent(opponentName)
	return {
		name = opponentName;
		won = 0;
		loss = 0;
		fightsWon = {};
		fightsLost = {};
	}
end

--
-- Retrieval
--

function Duelist_findDuels(duelTable,won,factionName,guildName,opponentName,minLevel,maxLevel,race,class)

	local _duels = {};
	local _factions = {};
	local _guilds = {};
	local _opponents = {};
	local _duels = {};
	
	if (factionName) then
		for i,_faction in ipairs(duelTable) do
			if(_faction.name == factionName) then
				table.insert(_factions,1,_faction);
				break;
			end
		end	
	else
		for i,_faction in ipairs(duelTable) do
			table.insert(_factions,1,_faction);
		end	
	end		
	
	if (table.getn(_factions) == 0) then
		return _duels;
	end
	
	if (guildName) then
		for i,_faction in ipairs(_factions) do
			for j,_guild in ipairs(_faction.guilds) do	
				if (_guild.name == guildName) then
					table.insert(_guilds,1,_guild);
					break;
				end	
			end
		end							
	else
		for i,_faction in ipairs(_factions) do
			for j,_guild in ipairs(_faction.guilds) do	
				table.insert(_guilds,1,_guild);
			end
		end
	end
	
	if (table.getn(_guilds) == 0) then
		return _duels;
	end
	
	if (opponentName) then
		for i,_guild in ipairs(_guilds) do
			for j,_opponent in ipairs(_guild.opponents) do
				if (_opponent.name == opponentName) then
					table.insert(_opponents,1,_opponent);
					break;
				end
			end
		end
	else
		for i,_guild in ipairs(_guilds) do
			for j,_opponent in ipairs(_guild.opponents) do
				table.insert(_opponents,1,_opponent);
			end
		end
	end
	
	if (table.getn(_opponents) == 0) then
		return _duels;
	end
	
	for i,_opponent in ipairs(_opponents) do
		if (won) then
			for j,_won in ipairs(_opponent.fightsWon) do
				table.insert(_duels,_won);
			end
	  else
			for j,_lost in ipairs(_opponent.fightsLost) do
				table.insert(_duels,_lost);
			end
		end
	end
	
	if (minLevel) then
		for i,_duel in ipairs(_duels) do
			if (_duel.level < minLevel) then
				table.remove(_duels,i);
				i = i - 1;
			end
		end
	end
	
	if (maxLevel) then
		for i,_duel in ipairs(_duels) do
			if (_duel.level > maxLevel) then
				table.remove(_duels,i);
				i = i - 1;
			end
		end
	end
	
	if (race) then		
		for i,_duel in ipairs(_duels) do
			if (_duel.race ~= race) then				
				table.remove(_duels,i);
				i = i - 1;
			end
		end
	end
	
	if (class) then
		for i,_duel in ipairs(_duels) do
			if (_duel.class ~= class) then
				table.remove(_duels,i);
				i = i - 1;
			end
		end
	end
	
	return _duels;
end

function Duelist_getFaction(factionName) 
	for i,_faction in ipairs(Duelist_CurrentPlayer.opponentsTable) do
		if (_faction.name == factionName) then
			return _faction;		
		end
	end
	return nil;
end

function Duelist_getGuild(factionName,guildName) 
  local _faction;
	for i,_f in ipairs(Duelist_CurrentPlayer.opponentsTable) do
		if (_f.name == factionName) then
			_faction = _f;	
			break;	
		end
	end
	
	if (not _faction) then
		return nil;
	end
	
	local _guild
	for i,_g in ipairs(_faction.guilds) do
		if (_g.name == guildName) then
			_guild = _g;		
			break;
		end
	end	

	if (_guild) then
		return _guild;
	else
		return nil;
	end
end

function Duelist_getOpponent(factionName,guildName,opponentName) 
	local _faction;
	for i,_f in ipairs(Duelist_CurrentPlayer.opponentsTable) do
		if (_f.name == factionName) then
			_faction = _f;		
			break;
		end
	end
	
	if (not _faction) then
		return nil;
	end
	
	local _guild;
	for i,_g in ipairs(_faction.guilds) do
		if (guildName) then
			if (_g.name == guildName) then				
				_guild = _g;	
				break;	
			end
		else
			if (not _g.name) then
				_guild = _g;	
				break;	
			end
		end
	end	

	if (not _guild) then
		return nil;
	end
	
	local _opponent;
	for i,_o in ipairs(_guild.opponents) do
		if (_o.name == opponentName) then
			_opponent = _o;		
			break;
		end
	end
	
	return _opponent;
end

--
-- Insert
--

function Duelist_addDuel(duel)
	local _done = false;
	for i,_faction in ipairs(Duelist_CurrentPlayer.opponentsTable) do
		if (_faction.name == duel.faction) then	
			Duelist_insertFactionDuel(_faction,duel);		
			_done = true;
		end
	end  
	
	if (not _done) then
		table.insert(Duelist_CurrentPlayer.opponentsTable,1,Duelist_newFaction(duel.faction));
		Duelist_insertFactionDuel(Duelist_CurrentPlayer.opponentsTable[1],duel);	
	end
	
	if (duel.won) then
		Duelist_CurrentPlayer.fightsWon = Duelist_CurrentPlayer.fightsWon + 1;
	else
		Duelist_CurrentPlayer.fightsLost = Duelist_CurrentPlayer.fightsLost + 1;
	end		
end

function Duelist_addGuild(faction,guildName)
	table.insert(faction.guilds,1,Duelist_newGuild(guildName));
end

function Duelist_addOpponent(guild,opponentName)
	table.insert(guild.opponents,1,Duelist_newOpponent(opponentName));
end;

function Duelist_insertFactionDuel(faction,duel)
	local _found = false;
	for i,_guild in ipairs(faction.guilds) do
		if (_guild.name == duel.guild) then
			Duelist_insertGuildDuel(_guild,duel);
			_found = true;						
		end
	end
	
	if (not _found) then
		Duelist_addGuild(faction,duel.guild);
		Duelist_insertGuildDuel(faction.guilds[1],duel);
	end
	
	if (duel.won) then
		faction.fightsWon = faction.fightsWon + 1;
	else
		faction.fightsLost = faction.fightsLost + 1;
	end
end
	
function Duelist_insertGuildDuel(guild,duel)
	local _found = false;
	for i,_opponent in ipairs(guild.opponents) do
		if (_opponent.name == duel.name) then
			Duelist_insertOpponentDuel(_opponent,duel);
			_found = true;
		end
	end
			
	if (not _found) then
		Duelist_addOpponent(guild,duel.name);
		Duelist_insertOpponentDuel(guild.opponents[1],duel);
	end
	
	if (duel.won) then
		guild.fightsWon = guild.fightsWon + 1;
	else
		guild.fightsLost = guild.fightsLost + 1;
	end
end;

function Duelist_insertOpponentDuel(opponent,duel) 
  if (duel.won) then
  	opponent.won = opponent.won + 1;
		table.insert(opponent.fightsWon,1,duel);
  else
    opponent.loss = opponent.loss + 1;		    
		table.insert(opponent.fightsLost,1,duel);
  end		 			
end;

--
-- Misc
-- 

function Duelist_updateHealth(duel,targetHealth,playerHealth)
	duel.health = targetHealth;
	duel.pHealth = playerHealth;
end

function Duelist_setResults(duel,finished,endTime,won,percentTarget,percentPlayer)
	duel.finished = finished;
	duel.duelEndTime = endTime;
	duel.won = won;
	duel.percentTarget = percentTarget;
	duel.percentPlayer = percentPlayer;
	Duelist_setScore(duel);
end

function Duelist_setScore(duel)
	local _score = Duelist_SCORE_BASE;
	local _leveldiff;
	
	if (duel.won) then
		_leveldiff = duel.level - duel.pLevel;
	else
		if (duel.pLevel > duel.level) then	
			_leveldiff = duel.level - duel.pLevel;
		else 
			_leveldiff = duel.pLevel - duel.level;
		end
	end
	
	local _levelmulti = Duelist_LEVEL_MULTIPLIER * _leveldiff;
	
	_score = _levelmulti * Duelist_SCORE_BASE + _score;
	
	local _healthdiff = math.abs(duel.percentTarget - duel.percentPlayer);
	local _healthmulti = Duelist_HEALTH_MULTIPLIER * _healthdiff;
	
	_score = _score - _healthmulti * Duelist_SCORE_BASE;
	
	duel.score = _score;
	duel.levelDiff = _leveldiff;
	duel.levelMulti = _levelmulti;
	duel.healthDiff =  _healthdiff;
	duel.healthMulti = _healthmulti;
end

--
-- Initialize
--

function Duelist_initialize()
	if (not Duelist_DEBUG) then
	  SlashCmdList["DUELIST"] = Duelist_command;
	    SLASH_DUELIST1 = "/duelist";
	    SLASH_DUELIST2 = "/dl";
	end
  
  if (not DuelDatabase) then
  	DuelDatabase = {};
  end
  
  Duelist_RealmName = string.gsub(GetCVar("realmName"),"'","");
  
  Duelist_print("DuelDatabase Version "..Duelist_DUELFRAME_VERSION.."."..Duelist_VERSION.." Loaded");
  Duelist_printBanner("DuelDatabase Version "..Duelist_DUELFRAME_VERSION.."."..Duelist_VERSION.." Loaded");
end		

function Duelist_databaseInitialize()
  -- Intialize Statics
	Duelist_Player = UnitName("player");  
	Duelist_Faction = UnitFactionGroup("player");
	
	
  if (not DuelDatabase[Duelist_Player] or not DuelDatabase[Duelist_Player].Version) then 
  	Duelist_print("Intializing for player " .. Duelist_Player); 
  
  	DuelDatabase[Duelist_Player] = {
  	
  	  name = Duelist_Player;
  	  class = UnitClass("player");
  	  race = UnitRace("player");
  	  faction = Duelist_Faction;
  	  realm = Duelist_RealmName;
  	  fightsWon = 0;
  	  fightsLost = 0;
  	  
    	opponentsTable = {};    	
    	
    	Version = Duelist_VERSION;
    }; 
  end
  
  if (not DuelDatabase[Duelist_Player].realm) then
  	DuelDatabase[Duelist_Player].realm = Duelist_RealmName;
  	Duelist_CurrentPlayer = DuelDatabase[Duelist_Player];
  else
    table.foreach(DuelDatabase,function(key,value)
    															if (value.name == Duelist_Player
    																	and value.realm == Duelist_RealmName) then
    																Duelist_print(value.realm .. ": " .. value.name .. " loaded");
    																Duelist_CurrentPlayer = value;
    														  end
    														end); 	
  end
  
  if (DuelistTable and DuelistTable[Duelist_Player]) then
  	Duelist_print("Database upgrade detected. Upgrading data tables for player " .. Duelist_Player);  	
 	
  	for i,_duel in ipairs(DuelistTable[Duelist_Player].DuelsWonTable) do
  		_duel.pname = nil;
  		_duel.pclass = nil;
  		_duel.prace = nil;
  		_duel.pfaction = nil;
  		
  		Duelist_Duel = Duelist_newDuel(
					_duel.name,
					_duel.level,
					_duel.class,
					_duel.race,
					_duel.faction,
					_duel.guild,
					_duel.health,
					_duel.maxhealth,
					_duel.plevel,
					_duel.pguild,
					_duel.phealth,
					_duel.pmaxhealth,
					_duel.location,
					_duel.sub,
					GetTime()
			);
  		
			Duelist_setResults(Duelist_Duel,true,GetTime(),true,_duel.percentTarget,_duel.percentPlayer);
			Duelist_addDuel(Duelist_Duel);
			_duel = nil;
  	end  	
  	
  	for i,_duel in ipairs(DuelistTable[Duelist_Player].DuelsLossTable) do
  		_duel.pname = nil;
  		_duel.pclass = nil;
  		_duel.prace = nil;
  		_duel.pfaction = nil;
						
			Duelist_Duel = Duelist_newDuel(
					_duel.name,
					_duel.level,
					_duel.class,
					_duel.race,
					_duel.faction,
					_duel.guild,
					_duel.health,
					_duel.maxhealth,
					_duel.plevel,
					_duel.pguild,
					_duel.phealth,
					_duel.pmaxhealth,
					_duel.location,
					_duel.sub,
					GetTime()
			);
				
  		Duelist_setResults(Duelist_Duel,true,GetTime(),false,_duel.percentTarget,_duel.percentPlayer);
			Duelist_addDuel(Duelist_Duel);
			_duel = nil;
  	end

  	
  	DuelistTable[Duelist_Player].DuelsWonTable = nil;
  	DuelistTable[Duelist_Player].DuelsLossTable = nil;  	
  	DuelistTable[Duelist_Player] = nil;
  	RegisterForSave("DuelistTable");
  end
  RegisterForSave("DuelDatabase");
end

--
-- Lib Functions
--

function Duelist_command(command)
	if (command == "delete-all-tables" 
			and DuelDatabase) then
		Duelist_print("Deleting all database tables");
		DuelDatabase = {};
		Duelist_databaseInitialize();	
	elseif (command == "delete-player-tables" and Duelist_CurrentPlayer) then
		Duelist_print("Deleting "..Duelist_Player.." tables");
		Duelist_CurrentPlayer = {};
		Duelist_databaseInitialize();	
	elseif (command == "force-update" 
					and DuelDatabase) then
		Duelist_print("Forcing update");
		Duelist_databaseInitialize();	
	end
end

function Duelist_duelFinished(message)
  
  if (Duelist_Duel and Duelist_Duel.finished) then
  	Duelist_Duel = nil;
  end
  
  if (Duelist_Duel) then  	
	  local _percentTarget = math.floor(Duelist_Duel.health * 100 / Duelist_Duel.maxHealth);
	  local _percentPlayer = math.floor(Duelist_Duel.pHealth * 100 / Duelist_Duel.pMaxHealth);	
	 
		if (_percentTarget < _percentPlayer) then
			Duelist_setResults(Duelist_Duel,true,GetTime(),true,_percentTarget,_percentPlayer);									
		else
			Duelist_setResults(Duelist_Duel,true,GetTime(),false,_percentTarget,_percentPlayer);
		end
		
		Duelist_addDuel(Duelist_Duel);
				
		RegisterForSave("DuelDatabase");
	end
end

--
-- Event Handlers
--

function Duelist_onUpdate()
	if (UnitName("target") ~= nil
			and UnitCanAttack("player", "target")
			and UnitIsPlayer("target")
			and Duelist_Faction == UnitFactionGroup("target")) then
				
		if (not Duelist_CurrentPlayer or Duelist_Player == Duelist_UNKNOWN) then
  		Duelist_databaseInitialize()	
  	end

		-- Race condition where DUEL_FINISHED is fired while still able to attack.
		-- This was causing erroneous duels to persist which lead to inaccurate 
		-- duel results. The below workaround should address this.
		if (Duelist_Duel and Duelist_Duel.finished) then
			if (Duelist_Duel.name == UnitName("target")) then 
				local _targetPercent = math.floor(UnitHealth("target") * 100 / UnitHealthMax("target"));
				local _playerPercent = math.floor(UnitHealth("player") * 100 / UnitHealthMax("player"));
				if(_targetPercent <= 20 or _playerPercent <= 20) then
					Duelist_Duel.finished = true;
					return;
				else
				  Duelist_Duel = nil;						
				end
			else
				Duelist_Duel = nil;
			end
		end

		if (not Duelist_Duel) then
			Duelist_print("Creating Duel: "..UnitName("target"));
			Duelist_Duel = Duelist_newDuel(
					UnitName("target"),
					UnitLevel("target"),
					UnitClass("target"),
					UnitRace("target"),
					UnitFactionGroup("target"),
					GetGuildInfo("target"),
					UnitHealth("target"),
					UnitHealthMax("target"),
					UnitLevel("player"),
					GetGuildInfo("player"),
					UnitHealth("player"),
					UnitHealthMax("player"),
					GetMinimapZoneText(),
					GetSubZoneText(),
					GetTime()
				);
		else
	  	Duelist_updateHealth(Duelist_Duel,UnitHealth("target"),UnitHealth("player"));
	  end	
  end
  
  if (Duelist_TEST
  		and UnitName("target") ~= nil
  		and UnitIsPlayer("target")) then
  		
  		if (not Duelist_Duel) then
			Duelist_print("Creating Duel: "..UnitName("target"));
			Duelist_Duel = Duelist_newDuel(
					UnitName("target"),
					UnitLevel("target"),
					UnitClass("target"),
					UnitRace("target"),
					UnitFactionGroup("target"),
					GetGuildInfo("target"),
					UnitHealth("target") - 2,
					UnitHealthMax("target"),
					UnitLevel("player"),
					GetGuildInfo("player"),
					UnitHealth("player"),
					UnitHealthMax("player"),
					GetMinimapZoneText(),
					GetSubZoneText(),
					GetTime()
				);
			
			Duelist_duelFinished();
		end	
  	
  end
end

--
-- Utility
--

function Duelist_print(msg)
  if (msg == nil) then
  	ChatFrame1:AddMessage("(DuelDatabase) nil", Duelist_COLORTABLE.r, Duelist_COLORTABLE.g, Duelist_COLORTABLE.b);	
  else
  	ChatFrame1:AddMessage("(DuelDatabase) "..msg, Duelist_COLORTABLE.r, Duelist_COLORTABLE.g, Duelist_COLORTABLE.b);
  end
end

function Duelist_printBanner(msg)
  UIErrorsFrame:AddMessage(msg, Duelist_COLORTABLE.r, Duelist_COLORTABLE.g, Duelist_COLORTABLE.b, 1.0, UIERRORS_HOLD_TIME);
end

--
-- Debug
--

if (Duelist_DEBUG) then
  PlayerName = "Player";
  TargetName = "Target";
  Player = 50;
  Target = 100;

  function UnitName(arg1)
    if (arg1 == "player") then
    	return PlayerName;
    else
    	return TargetName;
    end
  end

  function UnitLevel(arg1)
    return 18;
  end

  function UnitClass(arg1)
    return "Priest";
  end

  function UnitRace(arg1)
    return "Dwarf";
  end

  function UnitHealth(arg1)
    if (arg1 == "player") then
      return Player;
    else
      return Target;
    end;
  end;

  function UnitHealthMax(arg1)
    return 100;
  end

  function UnitCanAttack(arg1,arg2)
    return true;
  end

  function UnitIsPlayer(arg1)
    return true;
  end

  function UnitFactionGroup(arg1)
    return "Horde";
  end

  function GetGuildInfo(arg1)
    return "Test";
  end

  function RegisterForSave(arg1)

  end

  function Duelist_print(...)
    print(unpack(arg));
  end

  function Duelist_printBanner(...)

  end
  
  function GetMinimapZoneText()
    return "Undercity";
  end
  
  function GetSubZoneText()
  	return "The Canals";
  end
  
  function GetTime()
  	return 12312;
  end

  function TestDuelist(pName,tName,pHealth,tHealth)
    PlayerName = pName;
    TargetName = tName;
    Player = pHealth;
    Target = tHealth;

    Duelist_initialize();
    Duelist_databaseInitialize()
    Duelist_onUpdate();
    Duelist_duelFinished();
  end
end
