
--[[    
    Paranoia Enemy Player Alert
    Copyright (C) 2008 Victor Barrancos
  
    This file is part of Paranoia Enemy Player Alert.

    Paranoia Enemy Player Alert is free software: you can redistribute it 
    and/or modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation, either version 3 of the 
    License, or (at your option) any later version.

    Paranoia Enemy Player Alert is distributed in the hope that it will 
    be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 
    of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
    General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Paranoia Enemy Player Alert.  If not, see 
    <http://www.gnu.org/licenses/>.
]]--

--===================================================================================================================--
-- Name:        Paranoia Enemy Player Alert
-- Version:     1.12.2
-- Description: Paranoia is a WoW addon that attempts to detect enemy players/pets near the player. The addon detects
--              enemies by registering to COMBAT_LOG_EVENT_UNFILTERED and scanning for any events that are triggered
--              by enemy players, including hits, misses, dodges, spell casts, tradeskills, buffs, DoTs, etc.
--              Paranoia was originally written by rmet0815 (http://www.xs4all.nl/~rmetzger/paranoia). All updates after
--              WoW patch 2.4 were written by Lifetapt (http://www.leetsoft.net).
--              I am still a somewhat of a beginner to Lua, so if you see something that is inefficient or not well done, 
--              I'd really appreciate it if you pointed it out to me. I especially need help with writing optimized code, 
--              since I come from VB6 and VB.Net which both don't put much of an emphasis on performance/memory usage.
-- Authors:     Original (1.0-1.03): rmet0815      2.4 Update (1.04+): Lifetapt @ Alterac Mountains
--===================================================================================================================--
-- 1) Declarations
-- 2) Event Handlers
-- 3) Initialization Functions
-- 4) Database Functions
-- 5) Miscellaneous Helper Functions
-- 6) Slash Handling
-- 7) Hostile Player Handling
-- 8) Configuration Functions
-- 9) WarnFrame Functions
--10) Dropdown List Functions
--11) Minimap Button Functions
--===================================================================================================================--
--  1) Declarations
--===================================================================================================================--

Paranoia = {};
Paranoia_updater = CreateFrame("Frame", "ParanoiaUpdater");
local Dewdrop = AceLibrary("Dewdrop-2.0")

PARANOIA_VERSION = "1.12.2"
PARANOIA_COMMVERSION = "1"
PARANOIA_DBVERSION = 1.1

--Default Settings==================>
  Paranoia_Config = { enabled = true,
    disableInBattlefield = true,
    disableInArena = true,
    disableInSanctuaries = true,
    disableInFFA = true,
    hideList = false,
    listLocked = false,
    enableSound = true,
    inactivityFade = true,
    borderOpacity = 1,
    frameOpacity = 1,
    alertPopupEnabled = true,
    alertPopupfadeTime = 2,
    alertPopupdisplayTime = 3, 
    alertPopupAlpha = 1,
    listTimeout = 60,
    warnTimeout = 60,
    maxHostiles = 15,
    customMsg = PARANOIA_DEFAULTMSG,
    listScale = 1.0,
    enableComm = true,
    partyWarn = true,
    guildWarn = true,
    minimapPos = 172,
    hideMinimap = false,
  };

--Warning Variables=================>
local gLastwarnHostilePlayer = 0;

local gHostileList = {};
local gMaxHostiles = 50;
local gCurHostiles = 0;

local gNotHostileList = {};
local gMaxNotHostiles = 100;
local gCurNotHostiles = 0;

local gLastLookup = 0;

--Player Database Variables=========>
Paranoia_edb = {};
local edb = {};

--Fadeout Control===================>
local mainStartTime = 0;

--Ping Variables===================>
local isPingWaiting = false;
local gotFirstReply = false;
local lastReplyTime = 0;

--Miscellaneous Variables===========>
local haveVarsBeenLoaded = false;

local isParanoiaDisabled = false;
local isConfigOpen = false;

local debugEnabled = false;
local verboseEnabled = false;

local fontList = {"NumberFontNormal", "NumberFontNormalLarge", "NumberFontNormalHuge", "GameFontNormal", "GameFontNormalLarge", "GameFontNormalHuge", "ChatFontNormal", "SystemFont", "MailTextFontNormal", "QuestTitleFont"};

local configCopy = {}

--Announce Channel Variables========>
local channelid;
local channelname;

--Inactivity Fade Variables=========>
local timeSinceLast, fadeTicks, stepAlpha, textAlpha, colorR, colorG, colorB;

local oppFaction = PARANOIA_UNKNOWN;
local oppFactionEng = PARANOIA_UNKNOWN;

--Detection Variables===============>
local hostileSource = false;
local hostileDest = false;
local knownSkill = false;
local guessedClass = PARANOIA_UNKNOWN;
local guessedLevel = 0;
local otherBuff = 0;
local lastDamager = PARANOIA_UNKNOWN;
local lastDamagerTime = 0;

--Time Variables====================>
local secondsString, minutesString, hoursString, daysString;

--Dialog Boxes======================>
  StaticPopupDialogs["RELOADUIPROMPT"] = {
    text = PARANOIA_RELOADPROMPTTEXT,
    button1 = PARANOIA_YESPROMPT,
    button2 = PARANOIA_NOPROMPT,
    OnAccept = function()
      Paranoia:DoResetFrames();
    end,
    timeout = 0,
    whileDead = 1,
    hideOnEscape = 0,
    showAlert = 1,
  };
  
  StaticPopupDialogs["RELOADUIPROMPT2"] = {
    text = PARANOIA_DEFAULTPROMPTTEXT,
    button1 = PARANOIA_YESPROMPT,
    button2 = PARANOIA_NOPROMPT,
    OnAccept = function()
      Paranoia:DoResetSettings();
    end,
    timeout = 0,
    whileDead = 1,
    hideOnEscape = 0,
    showAlert = 1,
  };
  
  StaticPopupDialogs["PARANOIAHIDDEN"] = {
    text = string.format(PARANOIA_FIRSTRUNPROMPTTEXT, GetZoneText()),
    button1 = PARANOIA_YESPROMPT,
    button2 = PARANOIA_NOPROMPT,
    OnAccept = function()
      Paranoia:ShowConfig();
    end,
    timeout = 0,
    whileDead = 1,
    hideOnEscape = 0,
    showAlert = 1,
  };
  
  StaticPopupDialogs["PARANOIAKILLED"] = {
    text = "Paranoia: You were killed by "..lastDamager..". Do you wish to add this player to your KoS list? (Auto-dismissing in 6 seconds)",
    button1 = PARANOIA_YESPROMPT,
    button2 = PARANOIA_NOPROMPT,
    OnAccept = function()
      Paranoia:ShowConfig();
    end,
    timeout = 6,
    whileDead = 1,
    hideOnEscape = 1,
    showAlert = 1,
  };
  
  StaticPopupDialogs["PARANOIAPURGE"] = {
    text = "Paranoia: This realm's database contains over 1000 players. You may want to purge old players from the database to reduce Paranoia's memory usage.",
    button1 = PARANOIA_OKPROMPT,
    timeout = 10,
    whileDead = 1,
    hideOnEscape = 1,
    showAlert = 1,
  };

--Constants=========================>
local UNITFLAG_HOSTILEPC =  0x548;


--===================================================================================================================--
--  2) Event Handlers
--===================================================================================================================--

function Paranoia:OnLoad(self)

  SLASH_PARANOIACONF1 = PARANOIA_SLASH1;
  SLASH_PARANOIACONF2 = PARANOIA_SLASH2;
  SlashCmdList["PARANOIACONF"] = function(msg) Paranoia:SlashHandler(msg); end
  SLASH_PARANOIARL1 = "/rl";
  SlashCmdList["PARANOIARL"] = function(msg) ReloadUI(); end

  self:RegisterForDrag("LeftButton");
  
  self:RegisterEvent("PLAYER_ENTERING_WORLD");
  self:RegisterEvent("VARIABLES_LOADED");
  self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED"); -- main detection method, fires any time there's an incoming combat log event.
  self:RegisterEvent("PARTY_MEMBERS_CHANGED");
  self:RegisterEvent("ZONE_CHANGED");
  self:RegisterEvent("ZONE_CHANGED_NEW_AREA");
  self:RegisterEvent("PLAYER_REGEN_DISABLED");       -- entered combat
  self:RegisterEvent("PLAYER_REGEN_ENABLED");        -- left combat
  self:RegisterEvent("UPDATE_MOUSEOVER_UNIT");       -- when we mouseover something
  self:RegisterEvent("PLAYER_TARGET_CHANGED");       -- when we switch targets
  self:RegisterEvent("CHAT_MSG_ADDON");              -- when a groupie/guildie detects a hostile
  self:RegisterEvent("PLAYER_DEAD");
  self:RegisterEvent("DUEL_REQUESTED")               -- used to ignore the player we are dueling

  --Initialize button positions
  local i = 2;
  local thisItem;
  
  Paranoia_StatusFrame1:ClearAllPoints(); --Anchors the first button to the enemy list.
  Paranoia_StatusFrame1:SetPoint("TOPLEFT", "Paranoia_Main", "TOPLEFT", 8, -8);
  
  while (i <= 15) do --This loops through buttons 2-15 and anchors them to the button preceding it.
    thisItem = getglobal("Paranoia_StatusFrame"..i);
    thisItem:ClearAllPoints();
    thisItem:SetPoint("TOPLEFT", "Paranoia_StatusFrame"..(i - 1), "BOTTOMLEFT", 0, 0);
    i = i + 1;
  end
  
end

-- 1352 is the unitflag we want, (hexadecimal 0x548)
-- we want to search in arg5 and arg8 (sourceFlags and destFlags)
-- 4424 = hostile pet

function Paranoia:OnEvent(event)
  if (event == "PLAYER_ENTERING_WORLD") then
    if not (Paranoia_Main:IsUserPlaced()) or not (Paranoia_Warn:IsUserPlaced()) then 
      if Paranoia:IsPlayerInSanctuary() or Paranoia:IsPlayerInBattlefield() or Paranoia:IsPlayerInFFA() then
        Paranoia:Msg(string.format(PARANOIA_CHATFIRSTRUNHIDDEN, GetZoneText()));
        StaticPopup_Show ("PARANOIAHIDDEN");
      else
        Paranoia:Msg(PARANOIA_CHATFIRSTRUN); 
      end
      Paranoia_Main:SetUserPlaced(true);
      Paranoia_Warn:SetUserPlaced(true);
    end
    if Paranoia:IsPlayerInInstance() then 
      Paranoia:hideList(true); 
      Paranoia:Msg(PARANOIA_CHATINSTANCE);
    end
  elseif (event == "VARIABLES_LOADED") then
    Paranoia:VariblesLoaded();
    Paranoia:partyMembersChanged();
    DEFAULT_CHAT_FRAME:AddMessage(string.format("|cFFFF7D0A"..PARANOIA_CHATPARALOADED, PARANOIA_VERSION, "|r|cFF9482CALifetapt|r|cFFFF7D0A"));
    if (GetLocale() ~= "enUS" and GetLocale() ~= "enGB") and PARANOIA_CHATLOCALIZEDBY then
      DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A"..PARANOIA_CHATLOCALIZEDBY);
    end
--~     if Paranoia_edb.players[GetRealmName()][oppFactionEng].TotalPlayers > 1000 then StaticPopup_Show("PARANOIAPURGE"); end
  elseif (event == "COMBAT_LOG_EVENT_UNFILTERED") then
    
    if (debugEnabled and verboseEnabled) then Paranoia:dMsg(tostring(arg2).." \n|cFFFFFFFFSourceName:|r "..tostring(arg4).." \n|cFFFFFFFFSourceFlags:|r "..tostring(arg5).." \n|cFFFFFFFFDestName:|r "..tostring(arg7).." \n|cFFFFFFFFDestFlags:|r "..tostring(arg8).." \n|cFFFFFFFFSpell:|r "..tostring(arg9).." ("..tostring(arg10)..")"); end
    --compare sourceFlags (arg5) and destFlags (arg8).
    --if either are 0x548 (hostile) or 0x528 (neutral).
    
    local bit_band = _G.bit.band
    if bit_band(arg5, UNITFLAG_HOSTILEPC) == UNITFLAG_HOSTILEPC then
      Paranoia:dMsg("***Source is hostile.");
      hostileSource = true;
    end
    
    if bit_band(arg8, UNITFLAG_HOSTILEPC) == UNITFLAG_HOSTILEPC then
      Paranoia:dMsg("***Destination is hostile.");
      hostileDest = true;
    end
    
    --hostile action is true, so now we'll check to see if the event was caused by a spell/skill (as opposed to simply
    --environmental damage or a regular melee swing)
    --we will only check if the event was caused by a spell/skill if the hostile player is in the source flags, to avoid misguesses
    --for example, if the hostile player is a Paladin who just gained arcane intellect, the addon would misclassify them as a mage.
    --however, if otherBuff returns zero, this means the buff cannot be cast on other players, so we can use the destFlags.
      
      if (hostileSource == true) then --The source of the event is hostile.
        if arg7 == UnitName("player") then  --if the destination of the combat log event is the player, then store the attacker's name.
          lastDamagerTime = GetTime();
          lastDamager = arg4
        end
        if Paranoia_SpellList == nil then 
          Paranoia:dMsg("Paranoia's spell list is not in memory; loading spell list.");
          gLastwarnHostilePlayer = GetTime();
          Paranoia:LoadSpellList();
        end   --The spell list has been unloaded because of inactivity, we need to reload it first.
        if (string.find(arg2, "RANGE_") or string.find(arg2, "SPELL_") or string.find(arg2, "SPELL_PERIODIC_")) then  --Event was not environmental or melee swing
          if (Paranoia_SpellList[arg9] ~= nil) then
            guessedClass = Paranoia_SpellList[arg9].Class; 
            guessedLevel = Paranoia_SpellList[arg9].Level;
            knownSkill = true;
          else
            knownSkill = false; 
            guessedClass = PARANOIA_UNKNOWN;
            guessedLevel = 0;
          end
        else  --Event was environmental or melee swing.
          guessedClass = PARANOIA_UNKNOWN;
          guessedLevel = 0;
          knownSkill = false;
        end
        Paranoia:warnHostilePlayer(arg2, arg4, guessedClass, guessedLevel, 0);
      end
      
      if (hostileDest == true) then --The destination of the event is hostile (we don't try to guess class/level).
        if arg2 == "PARTY_KILL" and arg4 == UnitName("player") then
          if Paranoia_edb.players[GetRealmName()][oppFactionEng][arg7] ~= nil then
            Paranoia:incrementKill(arg7);
            Paranoia:dMsg("Player has killed "..arg7..". Incrementing kill count.");
          end
        end
        knownSkill = false; 
        guessedClass = PARANOIA_UNKNOWN;
        guessedLevel = 0;
        Paranoia:warnHostilePlayer(arg2, arg7, guessedClass, guessedLevel, 0);
      end
      
      hostileSource = false;
      hostileDest = false;
      knownSkill = false;
      guessedClass = PARANOIA_UNKNOWN;
      guessedLevel = 0;
      otherBuff = 0;
  elseif (event == "PLAYER_DEAD") then
    if lastDamager ~= PARANOIA_UNKNOWN and lastDamager ~= nil then
      if Paranoia_edb.players[GetRealmName()][oppFactionEng][lastDamager] ~= nil then
        Paranoia:incrementDeath(lastDamager);
        StaticPopupDialogs["PARANOIAKILLED"].text = "You were killed by "..lastDamager..". Do you wish to add this player to your KoS list? (Auto-dismissing in 6 seconds)"
        if debugEnabled then StaticPopup_Show("PARANOIAKILLED"); end
      end
    end
  elseif (event == "DUEL_REQUESTED") then
    Paranoia:addNotHostile(arg1, (60*3));
  elseif (event == "PARTY_MEMBERS_CHANGED") then
    Paranoia:partyMembersChanged();
  elseif (event == "ZONE_CHANGED" or event =="ZONE_CHANGED_NEW_AREA") then
    Paranoia:dMsg(event.." event fired!");
    Paranoia:OnUpdate();
  elseif (event == "PLAYER_REGEN_DISABLED") then
    Paranoia_inCombatHeader:SetText("|cFFFF0000"..PARANOIA_INCOMBAT.."|r")
  elseif (event == "PLAYER_REGEN_ENABLED") then
    Paranoia_inCombatHeader:SetText("")
  elseif (event == "UPDATE_MOUSEOVER_UNIT") then
    --unitisplayer() returns either true or nil, not false. Unitisfriend returns 1 or nil.
    if UnitIsPlayer("mouseover") ~= nil and UnitIsFriend("player","mouseover") ~= 1 then
      local guildName, _, _ = GetGuildInfo("mouseover");
      Paranoia:dMsg("Mouseover unit is a hostile player character.");
      Paranoia:warnHostilePlayer(event, UnitName("mouseover"), UnitClass("mouseover"), UnitLevel("mouseover"), 1, _, guildName);
    elseif UnitIsPlayer("mouseover") ~= nil then 
      Paranoia:addNotHostile(UnitName("mouseover"), (60*3)); 
    end
  elseif (event == "PLAYER_TARGET_CHANGED") then
    if (UnitIsPlayer("target") ~= nil and UnitIsFriend("player","target") ~= 1) then
      local guildName, _, _ = GetGuildInfo("mouseover");
      Paranoia:dMsg("Targeted unit is a hostile player character.");
      Paranoia:warnHostilePlayer(event, UnitName("target"), UnitClass("target"), UnitLevel("target"), 1, _, guildName);
    elseif UnitIsPlayer("target") ~= nil then 
      Paranoia:addNotHostile(UnitName("target"), (60*3));
    end
  elseif (event == "CHAT_MSG_ADDON") then --Arg1 = prefix, arg2 = msg (delimited by a caret [^]), arg3 = channel, arg4 = sender
    if Paranoia_Config.enableComm == false then return end
    if arg1 == "PEPA"  then
      local commVersion, locale, commandType, msg = string.split("%", arg2)
      if commandType == "DATA" then
        if arg4 == UnitName("player") then return; end
        if commVersion ~= PARANOIA_COMMVERSION then return; end
        local sourcetype = 0;
        
        if arg3 == "GUILD" then sourcetype = 3; end
        if arg3 == "PARTY" then sourcetype = 2; end
        
        local name, class, level = string.split("^", msg);
        
        if sourcetype == 2 and Paranoia_Config.partyWarn == true then
          Paranoia:warnHostilePlayer(event, name, class, level, sourcetype, arg4);
        end
        if sourcetype == 3 and Paranoia_Config.guildWarn == true then 
          if Paranoia:IsGuildieNearby(arg4) then
            Paranoia:warnHostilePlayer(event, name, class, level, sourcetype, arg4);
          end
        end
      elseif commandType == "PING" then
        Paranoia:dMsg("Recieved a PING from "..arg4.."... sending PONG.");
        Paranoia:commMsg("PONG", msg, arg3, "ALERT", arg4);
      elseif commandType == "PONG" then
        if isPingWaiting == false then return; end
        if arg4 == UnitName("player") then return; end
        if gotFirstReply == false then gotFirstReply = true; end
        if msg == UnitName("player") then 
          if locale ~= GetLocale() then Paranoia:Msg(arg4.." is using a different locale ("..locale.."). You may receive class names in a different language.");  end
          if commVersion ~= PARANOIA_COMMVERSION then Paranoia:Msg(arg4.." is using an incompatible version of Paranoia. Data sharing will be disabled for this player."); end
        end
        lastReplyTime = GetTime();
        Paranoia:dMsg("Recieved a PONG from "..arg4.."!")
      end
    end
  end
end

function Paranoia:OnDragStart()

  if Paranoia_Config.listLocked then return; end
  UIFrameFadeOut(Paranoia_Main, 0.3, Paranoia_Config.frameOpacity, 0.5);
  Paranoia_Main:StartMoving(); 

end

function Paranoia:OnDragStop()

  if Paranoia_Config.listLocked then return; end
  if Paranoia:round(Paranoia_Main:GetAlpha(), 2) ~= Paranoia_Config.frameOpacity then UIFrameFadeIn(Paranoia_Main, 0.3, 0.5, Paranoia_Config.frameOpacity); end
  Paranoia_Main:StopMovingOrSizing();

end

function Paranoia:OnUpdate()
  --OnUpdate is primarily responsible for hiding and showing Paranoia based upon the location of the current player, 
  --as well handling ping timeouts and unloading the SpellList after a minute of inactivity.
  
  if Paranoia_SpellList ~= nil then
    if (GetTime() - gLastwarnHostilePlayer) > 60 then
      Paranoia:dMsg("There has been no enemy activity in the past 60 seconds; unloading spell list from memory.");
      Paranoia_SpellList = nil;
    end
  end
  
  if isPingWaiting then
    if (GetTime() - lastReplyTime) > 4 then
      isPingWaiting = false;
      if not gotFirstReply then Paranoia:Msg("Couldn't find any player(s) using Paranoia. Why not spread the word? :D"); end
    end
  end
  
  if lastDamager ~= PARANOIA_UNKNOWN then
    if (GetTime() - lastDamagerTime) > 8 then
      lastDamagerTime = 0;
      lastDamager = PARANOIA_UNKNOWN;
    end
  end
  
  if Paranoia:IsPlayerInInstance() then return; end
  if haveVarsBeenLoaded then
  
    --This closes the frame once the fadeout is finished.
    if (not (mainStartTime == 0)) then
      if (GetTime() - mainStartTime) > 0.4 then
        Paranoia:dMsg("Closing main frame.");
        mainStartTime = 0;
        if Paranoia:round(Paranoia_Main:GetAlpha(), 2) == 0 then Paranoia_Main:Hide(); end
      end
    end
    Paranoia:updateVisibility();
    Paranoia:updateHostileList();
  end
  
end

--===================================================================================================================--
--  3) Initialization Functions
--===================================================================================================================--

function Paranoia:VariblesLoaded()

  local englishFaction, localizedFaction = UnitFactionGroup("player")
  if (englishFaction == "Horde") then 
    oppFaction = PARANOIA_ALLIANCE;
    oppFactionEng = "Alliance";
  else
    oppFaction = PARANOIA_HORDE; 
    oppFactionEng = "Horde";
  end
  -- set default settings if first run
  if (Paranoia_Config.enabled == nil) then Paranoia_Config.enabled = true; end
  if (Paranoia_Config.disableInBattlefield == nil) then Paranoia_Config.disableInBattlefield = true; end
  if (Paranoia_Config.disableInArena == nil) then Paranoia_Config.disableInArena = true; end
  if (Paranoia_Config.disableInSanctuaries == nil) then Paranoia_Config.disableInSanctuaries = true; end
  if (Paranoia_Config.disableInFFA == nil) then Paranoia_Config.disableInFFA = true; end
  if (Paranoia_Config.hideList == nil) then Paranoia_Config.hideList = false; end
  if (Paranoia_Config.listLocked == nil) then Paranoia_Config.listLocked = false; end
  if (Paranoia_Config.enableSound == nil) then Paranoia_Config.enableSound = true; end
  if (Paranoia_Config.inactivityFade == nil) then Paranoia_Config.inactivityFade = true; end
  if (Paranoia_Config.borderOpacity == nil) then Paranoia_Config.borderOpacity = 1; end
  if (Paranoia_Config.frameOpacity == nil) then Paranoia_Config.frameOpacity = 1; end
  if (Paranoia_Config.alertPopupEnabled == nil) then Paranoia_Config.alertPopupEnabled = true; end
  if (Paranoia_Config.alertPopupfadeTime == nil) then Paranoia_Config.alertPopupfadeTime = 2; end
  if (Paranoia_Config.alertPopupdisplayTime == nil) then Paranoia_Config.alertPopupdisplayTime = 3; end
  if (Paranoia_Config.alertPopupAlpha == nil) then Paranoia_Config.alertPopupAlpha = 1; end
  if (Paranoia_Config.listTimeout == nil) then Paranoia_Config.listTimeout = 60; end
  if (Paranoia_Config.warnTimeout == nil) then Paranoia_Config.warnTimeout = 60; end
  if (Paranoia_Config.maxHostiles == nil) then Paranoia_Config.maxHostiles = 15; end
  if (Paranoia_Config.customMsg == nil) then Paranoia_Config.customMsg = PARANOIA_DEFAULTMSG; end
  if (Paranoia_Config.listScale == nil) then Paranoia_Config.listScale = 1.0; end
  if (Paranoia_Config.enableComm == nil) then Paranoia_Config.enableComm = true; end
  if (Paranoia_Config.partyWarn == nil) then Paranoia_Config.partyWarn = true; end
  if (Paranoia_Config.guildWarn == nil) then Paranoia_Config.guildWarn = true; end
  if (Paranoia_Config.minimapPos == nil) then Paranoia_Config.minimapPos = 172; end
  if (Paranoia_Config.hideMinimap == nil) then Paranoia_Config.hideMinimap = false; end
  
  -- apply some settings
  if (Paranoia_Config.enabled) then
    ShowUIPanel(Paranoia_Main);
  else
    HideUIPanel(Paranoia_Main);
  end
  ShowUIPanel(Paranoia_Main);
  if (Paranoia_Config.listLocked) then
    Paranoia:setLock(true);
  else
    Paranoia:setLock(false);
  end
  Paranoia_Main:SetBackdropBorderColor(255, 255, 255, Paranoia_Config.borderOpacity);
  Paranoia_Main:SetAlpha(Paranoia_Config.frameOpacity);
  Paranoia_Main:SetScale(Paranoia_Config.listScale);
  Paranoia_Warn:SetAlpha(Paranoia_Config.alertPopupAlpha);
  
  edb = Paranoia_edb;
  
  Paranoia:createBlizOptions();
  Paranoia:loadDatabase();
  Paranoia:MinimapButton_Reposition();
  Paranoia:UpdateMinimapVisibility();
  
  haveVarsBeenLoaded = true;
  
  Paranoia_updater:SetScript("OnUpdate", function() Paranoia:OnUpdate(); end);
end

function Paranoia:loadDatabase()
  if Paranoia_edb == nil then Paranoia:createDefaultDB(); end
  if Paranoia_edb.dbversion ~= PARANOIA_DBVERSION then
    Paranoia:Msg("WARNING: Your current enemy database is incompatible with this version of Paranoia. The database will be reset.")
    Paranoia:createDefaultDB();
  end
  if not Paranoia_edb.players[GetRealmName()] then
    Paranoia:createNewRealm(GetRealmName());
  end
end

function Paranoia:createBlizOptions()

  -- create the interface options panels, they'll appear in inverse order, with the ones added first appearing last.
  local frame = CreateFrame("FRAME", nil);
  frame:SetScript("OnShow",function() Paranoia:ShowConfig() end);
  frame.name = "Paranoia";
  InterfaceOptions_AddCategory(frame);
  
  ParanoiaConf_Frame.name = PARANOIA_TABGENERAL;
  ParanoiaConf_Frame.parent = "Paranoia";
  ParanoiaConf_Frame.default = function() Paranoia:ResetSettings() end
  ParanoiaConf_Frame.okay = function() Paranoia:WarnFrameHide(false) end
  ParanoiaConf_Frame.cancel = function() Paranoia:WarnFrameHide(true) end
  InterfaceOptions_AddCategory(ParanoiaConf_Frame);
  
  ParanoiaConf_Appearance.name = PARANOIA_TABAPPEARANCE;
  ParanoiaConf_Appearance.parent = "Paranoia";
  ParanoiaConf_Appearance.default = function() Paranoia:ResetSettings() end
  ParanoiaConf_Appearance.okay = function() Paranoia:WarnFrameHide(false) end
  ParanoiaConf_Appearance.cancel = function() Paranoia:WarnFrameHide(true) end
  InterfaceOptions_AddCategory(ParanoiaConf_Appearance);
  
  ParanoiaConf_AlertPopup.name = PARANOIA_TABALERTPOPUP;
  ParanoiaConf_AlertPopup.parent = "Paranoia";
  ParanoiaConf_AlertPopup.default = function() Paranoia:ResetSettings() end
  ParanoiaConf_AlertPopup.okay = function() Paranoia:WarnFrameHide(false) end
  ParanoiaConf_AlertPopup.cancel = function() Paranoia:WarnFrameHide(true) end
  InterfaceOptions_AddCategory(ParanoiaConf_AlertPopup);
  
  ParanoiaConf_Announce.name = PARANOIA_TABANNOUNCE;
  ParanoiaConf_Announce.parent = "Paranoia";
  ParanoiaConf_Announce.default = function() Paranoia:ResetSettings() end
  ParanoiaConf_Announce.okay = function() Paranoia:WarnFrameHide(false) end
  ParanoiaConf_Announce.cancel = function() Paranoia:WarnFrameHide(true) end
  InterfaceOptions_AddCategory(ParanoiaConf_Announce);
  
  ParanoiaConf_Communication.name = PARANOIA_TABCOMM;
  ParanoiaConf_Communication.parent = "Paranoia";
  ParanoiaConf_Communication.default = function() Paranoia:ResetSettings() end
  ParanoiaConf_Communication.okay = function() Paranoia:WarnFrameHide(false) end
  ParanoiaConf_Communication.cancel = function() Paranoia:WarnFrameHide(true) end
  InterfaceOptions_AddCategory(ParanoiaConf_Communication);
end

--===================================================================================================================--
--  4) Database Functions
--===================================================================================================================--

function Paranoia:createDefaultDB() --clears the DB and creates a fresh copy with no realms or players.

  Paranoia_edb = {
    dbversion = 1.1,
    players = {},
  }
  edb = Paranoia_edb;
  
end

function Paranoia:createNewRealm(realmname) --adds a new realm to the db
  
  edb.players[realmname] = {
    Horde = {
      TotalPlayers = 0,
    },
    Alliance = {
      TotalPlayers = 0,
    },
    TotalKills = 0,
    TotalDeaths = 0,
  };
  
end

function Paranoia:createNewPlayer(name, level, class, guild, lastSeen, kos) --creates a new player with the given data
  
  if Paranoia:IsPlayerInBattlefield() then return; end
  if Paranoia:IsPlayerInArena() then return; end
  if Paranoia:IsPlayerInFFA() then return; end
  
  if edb.players[GetRealmName()][oppFactionEng][name] ~= nil then error("Paranoia:createNewPlayer - "..name.." already exists in enemy database (shouldn't ever happen!)"); end
  
  edb.players[GetRealmName()][oppFactionEng][name] = {};
  edb.players[GetRealmName()][oppFactionEng][name][1] = level;         --integer
  edb.players[GetRealmName()][oppFactionEng][name][2] = class;         --integer
  edb.players[GetRealmName()][oppFactionEng][name][3] = guild;         --string
  edb.players[GetRealmName()][oppFactionEng][name][4] = lastSeen;      --integer
  edb.players[GetRealmName()][oppFactionEng][name][5] = kos;           --bool
  edb.players[GetRealmName()][oppFactionEng][name][6] = 0;             --integer (kills)
  edb.players[GetRealmName()][oppFactionEng][name][7] = 0;             --integer (deaths)
  edb.players[GetRealmName()][oppFactionEng][name][8] = "";            --string (player note)
  edb.players[GetRealmName()][oppFactionEng].TotalPlayers = edb.players[GetRealmName()][oppFactionEng].TotalPlayers + 1;
  
  
end

function Paranoia:updatePlayer(name, level, class, guild, lastSeen, kos, kills, deaths, note) --updates a player in the database. any values that are passed nil will not be changed.
  
  if Paranoia:IsPlayerInBattlefield() then return; end
  if Paranoia:IsPlayerInArena() then return; end
  if Paranoia:IsPlayerInFFA() then return; end
    
  if edb.players[GetRealmName()][oppFactionEng][name] ~= nil then
    if level then edb.players[GetRealmName()][oppFactionEng][name][1] = level; end
    if class then edb.players[GetRealmName()][oppFactionEng][name][2] = class; end
    if guild then edb.players[GetRealmName()][oppFactionEng][name][3] = guild; end
    if lastSeen then edb.players[GetRealmName()][oppFactionEng][name][4] = lastSeen; end
    if kos ~= nil then edb.players[GetRealmName()][oppFactionEng][name][5] = kos; end
    if kills then edb.players[GetRealmName()][oppFactionEng][name][6] = kills; end
    if deaths then edb.players[GetRealmName()][oppFactionEng][name][7] = deaths; end
    if note then edb.players[GetRealmName()][oppFactionEng][name][8] = note; end
  end
  
end

function Paranoia:incrementKill(name) --increments the kill count of the given player by 1.
  
  if Paranoia:IsPlayerInBattlefield() then return; end
  if Paranoia:IsPlayerInArena() then return; end
  if Paranoia:IsPlayerInFFA() then return; end
  
  if edb.players[GetRealmName()][oppFactionEng][name] ~= nil then
    edb.players[GetRealmName()][oppFactionEng][name][6] = edb.players[GetRealmName()][oppFactionEng][name][6] + 1;
    edb.players[GetRealmName()].TotalKills = edb.players[GetRealmName()].TotalKills + 1;
  end
end

function Paranoia:incrementDeath(name)  --increments the death count of the given player by 1.
  
  if Paranoia:IsPlayerInBattlefield() then return; end
  if Paranoia:IsPlayerInArena() then return; end
  if Paranoia:IsPlayerInFFA() then return; end
  
  if edb.players[GetRealmName()][oppFactionEng][name] ~= nil then
    edb.players[GetRealmName()][oppFactionEng][name][7] = edb.players[GetRealmName()][oppFactionEng][name][7] + 1;
    edb.players[GetRealmName()].TotalDeaths = edb.players[GetRealmName()].TotalDeaths + 1;
  end
end

function Paranoia:setKoS(name, isKoS)

  if isKoS == true then
    if edb.players[GetRealmName()][oppFactionEng][name][5] == true then return; end
    edb.players[GetRealmName()][oppFactionEng][name][5] = true;
    Paranoia:Msg(name.." is now flagged as Kill-on-Sight.");
  else
    if edb.players[GetRealmName()][oppFactionEng][name][5] == false then return; end
    edb.players[GetRealmName()][oppFactionEng][name][5] = false;
    Paranoia:Msg(name.." is no longer flagged as Kill-on-Sight.");
  end

end

function Paranoia:deletePlayer(name)  --removes a player from the database.
  
  if edb.players[GetRealmName()][oppFactionEng][name] == nil then error("Paranoia:deletePlayer - "..name.." doesn't exist in enemy database."); end
  
  edb.players[GetRealmName()][oppFactionEng][name] = nil;
  edb.players[GetRealmName()][oppFactionEng].TotalPlayers = edb.players[GetRealmName()][oppFactionEng].TotalPlayers - 1;
  
end

function Paranoia:getPlayerLevelClass(name) --returns level and class.

  if edb.players[GetRealmName()][oppFactionEng][name] ~= nil then
    return edb.players[GetRealmName()][oppFactionEng][name][1], edb.players[GetRealmName()][oppFactionEng][name][2];
  end
  return nil;
  
end


--===================================================================================================================--
--  5) Miscellaneous Helper Functions
--===================================================================================================================--

function Paranoia:ButtonClick(self, name)
    
    if (string.find(name, "emptyButton") ~= nil) then return; end
    Paranoia:dMsg("Class= "..gHostileList[name].class.." classKnown="..tostring(gHostileList[name].classKnown));
    Paranoia:dMsg("Level= "..gHostileList[name].level.." levelDisp="..gHostileList[name].level.." levelKnown="..tostring(gHostileList[name].levelKnown));
    Paranoia:dMsg("Zone= "..gHostileList[name].zone.." X Coordinate="..gHostileList[name].x.." Y Coordinate="..tostring(gHostileList[name].y));
    if self:CanChangeProtectedState() then
      self:SetAttribute("macrotext", "/target "..tostring(name));
      Paranoia:dMsg("Attempting to target "..tostring(name));
    else
      DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A"..PARANOIA_CHATTARGETDISABLED);
    end
    
    
end

function Paranoia:commMsg(command, msg, channel, priority, whisperTar)
  
  if priority == nil then priority = "BULK"; end
  ChatThrottleLib:SendAddonMessage(priority, "PEPA", PARANOIA_COMMVERSION.."%"..GetLocale().."%"..command.."%"..msg, string.upper(channel), whisperTar);
  
end

function Paranoia:dMsg(msg)
  
  if debugEnabled then DEFAULT_CHAT_FRAME:AddMessage("|cFF9482CAPEPADebug: "..msg.."|r"); end
  
end

function Paranoia:DoResetFrames()

  Paranoia_Warn:SetUserPlaced(false);
  Paranoia_Main:SetUserPlaced(false);
  ReloadUI();
  
end

function Paranoia:DoResetSettings()

  Paranoia_Config = nil;
  Paranoia_Warn:SetUserPlaced(false);
  Paranoia_Main:SetUserPlaced(false);
  ReloadUI()
  
end

function Paranoia:strLevel(name, level, source) --This converts a numeric level returned from UnitLevel to a string level.
  Paranoia:dMsg("name: "..name.." | level: "..level.." | source: "..source);
  if gHostileList[name].levelKnown ~= true then
    if (level == 80) then                         --level is 80, we don't append a + sign and we set levelKnown to true.
      gHostileList[name].level = 80;
      gHostileList[name].levelDisp = 80;
      gHostileList[name].levelKnown = true;
    elseif (level == -1) then                     --level is -1, so player is at least 10 levels higher. levelDisp = player level+10.
      gHostileList[name].level = level;
      gHostileList[name].levelDisp = (UnitLevel("player") + 10).."+";
    elseif (level ~= 0) then                      --level is known or guessed.
      gHostileList[name].level = level;
      if source ~= 1 then                         --source is not a mouseover/target, so it must have been a guess. levelknown = false, append a + sign.
        gHostileList[name].levelDisp = level.."+";
        gHostileList[name].levelKnown = false;
      else                                        --source is a mouseover/target and level is 100% sure. levelKnown = true.
        gHostileList[name].levelDisp = level;
        gHostileList[name].levelKnown = true;
      end
    else                                          --level is completely unknown (detected via environmental dmg/melee swing)
      gHostileList[name].level = 0;
      gHostileList[name].levelKnown = false;
      gHostileList[name].levelDisp = '?';
    end
  end
end

function Paranoia:numLevel(value) --Strips the + sign from the level.

  if strlen(value) == 2 then
    if strsub(value, 2, 2) == "+" then return tonumber(strsub(value, 1, 1)); end
  else
    if strsub(value, 3, 3) == "+" then return tonumber(strsub(value, 1, 2)); end
  end
  
end

function Paranoia:hideList(disabled)

  if Paranoia:round(Paranoia_Main:GetAlpha(), 2) == Paranoia:round(Paranoia_Config.frameOpacity, 2) then
    Paranoia:dMsg("Hiding main frame.");
    mainStartTime = GetTime();
    UIFrameFadeOut(Paranoia_Main, 0.2, Paranoia_Config.frameOpacity, 0);
  end
  if (disabled) then isParanoiaDisabled = true; end
  
end

function Paranoia:IsGuildieNearby(name)

  GuildRoster();
  for i = 1, (GetNumGuildMembers(true)) do
    local gname, _, _, _, _, gzone, _, _, _, _ = GetGuildRosterInfo(i)
    if gname == name then 
      if gzone ~= GetZoneText() then return(false); end
      return(true);
    end
  end
  return(false);
  
end

function Paranoia:IsPlayerInArena()

  if (IsActiveBattlefieldArena()) then
    return(true);
  end
  return(false);

end

function Paranoia:IsPlayerInBattlefield()

  local inInstance, instanceType = IsInInstance()
  if inInstance ~= nil and instanceType == "pvp" then return(true); else return(false); end
  return(false);

end

function Paranoia:IsPlayerInFFA()

    local isFFA = UnitIsPVPFreeForAll("player")
    if isFFA == nil then isFFA = false; end
    return isFFA;

end

function Paranoia:IsPlayerInInstance()

  local inInstance, instanceType = IsInInstance()
  if inInstance ~= nil and (instanceType == "party" or instanceType == "raid") then return(true); else return(false); end
  return(false);  

end


function Paranoia:IsPlayerInSanctuary()

    local pvpType, isFFA, faction = GetZonePVPInfo();
    isFFA = nil;
    faction = nil;
    if (pvpType == "sanctuary") then
        return(true);
    else
        return(false);
    end

end

function Paranoia:Msg(msg)

  DEFAULT_CHAT_FRAME:AddMessage("|cFF9482CAParanoia:|r |cFFFF7D0A"..msg.."|r");

end

function Paranoia:ResetFrames()

  StaticPopup_Show("RELOADUIPROMPT");
  
end

function Paranoia:ResetSettings()
  
  
  StaticPopup_Show ("RELOADUIPROMPT2");
  Paranoia_Warn:Clear();
  
end

function Paranoia:setLock(lock)

  local i = 1;

  if (lock) then
    Paranoia_Main:RegisterForDrag();
    while (i <= 15) do        --15 is the maximum number of lines allowed by the paranoia code
      button = getglobal("Paranoia_StatusFrame"..i);
      button:RegisterForDrag();
      i = i + 1;
    end
  else
    Paranoia_Main:RegisterForDrag("LeftButton");
    while (i <= 15) do        --15 is the maximum number of lines allowed by the paranoia code
      button = getglobal("Paranoia_StatusFrame"..i);
      button:RegisterForDrag("LeftButton");
      i = i + 1;
    end
  end
  
end

function Paranoia:showList(enabled)

  if (Paranoia_Config.hideList == false) then
    ShowUIPanel(Paranoia_Main);
    UIFrameFadeIn(Paranoia_Main, 0.2, 0, Paranoia_Config.frameOpacity)
  end
  if (enabled) then isParanoiaDisabled = false; end
end

function Paranoia:round(num, decimalplaces)

  local mult = 10^(decimalplaces or 0)
  return math.floor(num * mult + 0.5) / mult
  
end

function Paranoia:ToBoolean(value)

  if (value == nil) then
    return(false);
  end

  if (type(value) == 'boolean') then
    return(value);
  end
  return(true);

end

function Paranoia:TimeToString(time, useShort)
  if useShort then
    secondsString = "seconds";
    minutesString = "minutes";
    hoursString = "hours";
    daysString = "days"
  else
    secondsString = "s";
    minutesString = "m";
    hoursString = "h";
    daysString = "d";
  end
  local ret = "";
  local units = 0;

  if( time < 0 ) then
    return "";
  end

  local days = floor( time / 86400 );

  if( days > 0 ) then
    time = time - days * 86400;
    ret = days.." "..daysString.." ";
    units = units + 1;
  end

  local hours = floor( time / 3600 );
  if( hours > 0 ) then
    time = time - hours * 3600;
    ret = ret .. hours.." "..hoursString.." ";
    units = units + 1;
  end

  if( units == 2 ) then
    return ret;
  end

  local minutes = floor ( time / 60 );
  if( minutes > 0 ) then
    time = time - minutes * 60;
    ret = ret .. minutes.." "..minutesString.." ";
    units = units + 1;
  end

  if( units == 2 ) then
    return ret;
  end

  return ret .. time.." "..secondsString;
end

function Paranoia:updateVisibility()

  if Paranoia_Config.enabled == false then Paranoia:hideList(true); return; end
  if Paranoia_Config.disableInBattlefield and Paranoia:IsPlayerInBattlefield() then Paranoia:hideList(true); return; end
  if Paranoia_Config.disableInArena and Paranoia:IsPlayerInArena() then Paranoia:hideList(true); return; end
  if Paranoia_Config.disableInFFA and Paranoia:IsPlayerInFFA() then Paranoia:hideList(true); return; end
  if Paranoia_Config.disableInSanctuaries and Paranoia:IsPlayerInSanctuary() == true then Paranoia:hideList(true); return; end
  if Paranoia_Config.hideList then Paranoia:hideList(false); return; end --Passing false to hideList will hide Paranoia but not disable it.
  if Paranoia_Main:GetAlpha() == 0 then Paranoia:showList(true); end

end

--===================================================================================================================--
--  6) Slash Handling
--===================================================================================================================--

--The code below for parsing slash command arguments was written by Tigerheart.
function Paranoia:GetArgument(msg)
 	if msg then
 		local a,b=strfind(msg, "(%S+)");
 		if a then
 			return strsub(msg,1,a-1), strsub(msg, b+1);
 		else	
 			return "";
 		end
 	end
end

function Paranoia:GetCmd(msg)
  if msg then
 		local a,b,c=strfind(msg, "(%S+)"); 
 		if a then
 			return c, strsub(msg, b+2);
 		else	
 			return "";
 		end
 	end
end

function Paranoia:SlashHandler(arg)
  local cmd, subCmd = Paranoia:GetCmd(arg);
  if arg then
    if arg ~= "testwarn" then arg = string.lower(arg); end
  end
  if (cmd == PARANOIA_SLACONFIG) then --config
    Paranoia:ShowConfig();
  elseif (cmd == PARANOIA_SLAENABLE) then--enable
    Paranoia_Config.enabled = true;
    Paranoia:OnUpdate();
    Paranoia:Msg(PARANOIA_CHATENABLED);
  elseif (cmd == PARANOIA_SLADISABLE) then --disable
    Paranoia_Config.enabled = false;
    Paranoia:Msg(PARANOIA_CHATDISABLED);
  elseif (cmd == PARANOIA_SLADEBUG) then --debug
    if (debugEnabled) then 
      debugEnabled = false;
      verboseEnabled = false;
      Paranoia:Msg(PARANOIA_CHATDEBUGDISABLED);
    else
      debugEnabled = true;
      Paranoia:Msg(PARANOIA_CHATDEBUGENABLED);
    end
  elseif (cmd == "verbose") then
    if (verboseEnabled) then 
      verboseEnabled = false;
      Paranoia:Msg("Verbose debug mode disabled.");
    else
      verboseEnabled = true;
      debugEnabled = true;
      Paranoia:Msg("Verbose debug mode enabled.");
    end
  elseif (cmd == "debuglist") then    -- not included in localization
    Paranoia:Msg("Debug Command List");
    DEFAULT_CHAT_FRAME:AddMessage("------------------------------------");
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia testwarn [name] [class] [level]|r\nSimulates a test hostile player. With no arguments, uses Testplayer1-10. ");
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia rui|r\nReloads the user interface. ");
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia default|r\nResets settings to default values (reloads UI).");
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia clear|r\nResets timeouts, clears the enemy/ignore lists, and unloads the SpellList.");
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia cleardb|r\nClears the entire enemy database (reloads UI).");
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia clearrealm|r\nClears the enemy database for this realm only.");
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia verbose|r\nToggles verbose debug mode (displays data about all incoming combatlog events).");
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia gc|r\nForces a garbage collection.");
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia mem|r\nDisplays Paranoia's memory usage.");
    if GetLocale() ~= "enUS" and GetLocale() ~= "enGB" then DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia ping [party|guild|player] [playername]|r\nLooks for other players using Paranoia."); end
  elseif (cmd == "testwarn") then
    local testName, testInfo = Paranoia:GetCmd(subCmd);
    if testName == "" then
      Paranoia:Msg("Calling Paranoia:warnHostilePlayer (using Testplayer1-10)");
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer", PARANOIA_DRUID, 4, 0);
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer2", PARANOIA_MAGE,68, 0);
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer3", PARANOIA_PALADIN, 35, 0);
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer4", PARANOIA_ROGUE, 10, 0);
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer5", PARANOIA_SHAMAN, 60, 0);
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer6", PARANOIA_WARLOCK, 41, 0);
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer7", PARANOIA_WARRIOR, 52, 0);
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer8", PARANOIA_PRIEST, 21, 0);
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer9", PARANOIA_HUNTER, 70, 0);
      Paranoia:warnHostilePlayer("User initiated test warning", "Testplayer10", nil, 50, 0);
    elseif testName ~= "" then
      local testClass, testLevel = Paranoia:GetCmd(testInfo);
      Paranoia:Msg("Calling Paranoia:warnHostilePlayer (using "..testName..")");
      Paranoia:warnHostilePlayer("User initiated test warning", testName, tostring(testClass), testLevel, 1);
    end
  elseif (cmd == "rui") then
    ReloadUI()
  elseif (cmd == "default") then
    Paranoia:ResetSettings()
  elseif (cmd == "clear") then
    Paranoia_SpellList = nil;
    gHostileList = {};
    gCurHostiles = 0;
    gNotHostileList = {};
    gCurNotHostiles = 0;
    gLastwarnHostilePlayer = 0;
    Paranoia:Msg("Enemy player table cleared.");
  elseif (cmd == "cleardb") then
    Paranoia:createDefaultDB();
    ReloadUI();
  elseif (cmd == "clearrealm") then
    Paranoia:createNewRealm(GetRealmName());
    Paranoia:Msg("All player data for this realm has been cleared.");
  elseif (cmd == "gc") then
    Paranoia:Msg("Forcing garbage collection...");
    collectgarbage();
    Paranoia:Msg("Garbage collection finished.");
  elseif (cmd == "mem") then
    UpdateAddOnMemoryUsage();
    Paranoia:Msg("Using "..Paranoia:round(GetAddOnMemoryUsage("Paranoia_EPA"),2).."kb of memory... type /para gc for garbage collection.");
  elseif cmd == "ping" then
    if isPingWaiting then
      Paranoia:Msg("Paranoia is waiting for a reply for a previous ping... please wait.");
      return;
    end
    local pingCmd, pingTarget = Paranoia:GetCmd(subCmd);
    if pingCmd == "party" then
      if GetNumPartyMembers() == 0 then
        Paranoia:Msg("You are not in a party.");
        return;
      end
      Paranoia:Msg("Pinging party members, please wait a moment...");
      Paranoia:commMsg("PING", UnitName("player"), "PARTY", "ALERT");
      gotFirstReply = false;
      lastReplyTime = GetTime();
      isPingWaiting = true;
    elseif pingCmd == "guild" then
      if IsInGuild() == nil then
        Paranoia:Msg("You are not in a guild.")
        return;
      end
      Paranoia:Msg("Pinging guild members, please wait a moment...");
      Paranoia:commMsg("PING", UnitName("player"), "GUILD", "ALERT");
      gotFirstReply = false;
      lastReplyTime = GetTime();
      isPingWaiting = true;
    elseif pingCmd == "player" then
      if pingTarget == "" then
        Paranoia:Msg("You must specify a player.");
        return;
      end
      if strlower(pingTarget) == strlower(UnitName("player")) then
        Paranoia:Msg(UnitName("player").." is almost certainly using Paranoia.");
        return;
      end
      Paranoia:Msg("Pinging player, please wait a moment...");
      Paranoia:commMsg("PING", UnitName("player"), "WHISPER", "ALERT", pingTarget);
      gotFirstReply = false;
      lastReplyTime = GetTime();
      isPingWaiting = true;
    else
      Paranoia:Msg("You must specify party, guild, or player.");
    end
  elseif cmd == nil or cmd == "" then
    Paranoia:Msg(PARANOIA_CHATSLASHLIST);
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A"..PARANOIA_CHATSLASHLISTCONFIG);
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A"..PARANOIA_CHATSLASHLISTENABLE);
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A"..PARANOIA_CHATSLASHLISTDISABLE);
    if GetLocale() == "enUS" or GetLocale() == "enGB" then DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A/paranoia ping [party|guild|player] [playername]|r\nLooks for other players using Paranoia."); end
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A"..PARANOIA_CHATSLASHLISTDEBUG);
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF7D0A"..PARANOIA_CHATSLASHLISTDEBUGL);
  else
    Paranoia:Msg(string.format(PARANOIA_CHATUNRECOGNIZED, cmd));
  end
  
end



--===================================================================================================================--
--  7) Hostile Handling Functions
--===================================================================================================================--
--*********************************************************************************************
--*********************************** REWRITE ME!!!! ******************************************
--*********************************************************************************************
--need to fix:
--adding/updating/retrieving players from database
--converting levels returned from unitlevel
--updating data in the right situations
--split up warnhostileplayer into different functions, clean it up

function Paranoia:addNotHostile(name, expiry)

  if expiry == nil then expiry = (60*60*3); end
  
  if (gNotHostileList[name]) then
    gNotHostileList[name].time = GetTime();
  elseif (gCurNotHostiles < gMaxNotHostiles) then
    gNotHostileList[name] = { time = GetTime(), timeout = tonumber(expiry) };
    gCurNotHostiles = gCurNotHostiles + 1;
  end

end

function Paranoia:partyMembersChanged()

  local i;

  Paranoia:addNotHostile(UnitName('player'), (60*60*3));
  if (UnitExists('pet')) then
    Paranoia:addNotHostile(UnitName('pet'), (60*60*3));
  end

  for i = 1, GetNumPartyMembers() do
    if (UnitExists('party'..i)) then
      Paranoia:addNotHostile(UnitName('party'..i), (60*60*3));
    end
    if (UnitExists('partypet'..i)) then
      Paranoia:addNotHostile(UnitName('partypet'..i), (60*60*3));
    end
  end

end

--this whole stupid thing needs to be rewritten, its bloated and ugly as hell, just like onupdate was. 
--i'll do it one day when I have lots of time to kill. Sorry for the excessive commenting, but it's the
--only way I'll be able to follow the code.
function Paranoia:warnHostilePlayer(event, name, class, level, source, sender, guild) --source is 0 if combat log event, 1 if mouseover/target, 2 if from party, 3 if from guild.

  if not (Paranoia_Config.enabled and name) then
    return;
  end
  
  if (class == nil or class == "") then class = PARANOIA_UNKNOWN; end                               
  if (level == nil or level == "") then level = 0; end  
  if (guild == nil) then guild = ""; end
  
  Paranoia:dMsg("warnHostilePlayer: event: "..event.." \nname: "..name.." \nclass: "..class.." \nlevel: "..level);
  
  if (gHostileList[name]) then  --the player is already on the enemy list, update info.
  
    gHostileList[name].time = GetTime();  --update time
    
    local posX, posY = GetPlayerMapPosition("player");  --update positioning
    posX=floor(posX *100);
    posY=floor(posY *100);
    gHostileList[name].x = posX;
    gHostileList[name].y = posY;
    gHostileList[name].zone = GetMinimapZoneText();
    
    if (class ~= PARANOIA_UNKNOWN) then  --update class
      gHostileList[name].class = class;
    end
    
    if type(level) == "string" then level = tonumber(level); end  --convert level to a number
    if type(gHostileList[name].level) == "string" then gHostileList[name].level = tonumber(gHostileList[name].level); end
    
    if (level ~= 0 and gHostileList[name].levelKnown == false and level > gHostileList[name].level) then
      gHostileList[name].level = level;
      Paranoia:strLevel(name, level, source);
    end
    
    -- communication
    if (GetTime() - gHostileList[name].lastComm > 6 or source == 1) and (Paranoia_Config.enableComm) and (source < 2) then
      Paranoia:commMsg("DATA", name.."^"..gHostileList[name].class.."^"..gHostileList[name].level.."^"..gHostileList[name].x..",".."^"..gHostileList[name].y, "PARTY");
      if IsInGuild() ~= nil then Paranoia:commMsg("DATA", name.."^"..gHostileList[name].class.."^"..gHostileList[name].level.."^"..gHostileList[name].x..",".."^"..gHostileList[name].y, "GUILD"); end
      gHostileList[name].lastComm = GetTime();
    end
    
    Paranoia:updatePlayer(name, Paranoia:numLevel(gHostileList[name].levelDisp), gHostileList[name].class, guild, time())
    
  elseif (gNotHostileList[name] and not Paranoia:IsPlayerInFFA()) then --player is on the ignore list (we ignore the ignore list when in FFA zones)
    gNotHostileList[name].time = GetTime();
    Paranoia:OnUpdate();
    return;
  else  --player is not on the hostile list, and is not on the ignore list, so we'll add them.
    Paranoia:addNewHostile(name, class, level, source, guild);
  end

  if (GetTime() - gLastwarnHostilePlayer > Paranoia_Config.warnTimeout) then
    if not (isParanoiaDisabled) then
      if Paranoia_Config.enableSound then
        Paranoia:dMsg("Playing warning sound...");
        PlaySoundFile("Sound\\Spells\\PVPWarningAlliance.wav");
        gLastwarnHostilePlayer = GetTime();
      end
      if gHostileList[name].class == PARANOIA_UNKNOWN then
        if (isConfigOpen == false and Paranoia_Config.alertPopupEnabled == true) then
          if source == 2 then 
            Paranoia_Warn:AddMessage(string.format(PARANOIA_PARTYALERTNOCLASS, sender, name, gHostileList[name].x, gHostileList[name].y), 255, 0, 0, 1, Paranoia_Config.alertPopupdisplayTime); 
          elseif source == 3 then
            Paranoia_Warn:AddMessage(string.format(PARANOIA_GUILDALERTNOCLASS, sender, name, gHostileList[name].x, gHostileList[name].y), 255, 0, 0, 1, Paranoia_Config.alertPopupdisplayTime); 
          else
            Paranoia_Warn:AddMessage(string.format(PARANOIA_ALERTNOCLASS, oppFaction, name), 255, 0, 0, 1, Paranoia_Config.alertPopupdisplayTime); 
          end
        end
        gLastwarnHostilePlayer = GetTime();
      else
        if (isConfigOpen == false and Paranoia_Config.alertPopupEnabled == true) then 
          if source == 2 then 
            Paranoia_Warn:AddMessage(string.format(PARANOIA_PARTYALERTCLASS, sender, gHostileList[name].class, name, gHostileList[name].x, gHostileList[name].y), 255, 0, 0, 1, Paranoia_Config.alertPopupdisplayTime); 
          elseif source == 3 then
            Paranoia_Warn:AddMessage(string.format(PARANOIA_GUILDALERTCLASS, sender, gHostileList[name].class, name, gHostileList[name].x, gHostileList[name].y), 255, 0, 0, 1, Paranoia_Config.alertPopupdisplayTime); 
          else
            Paranoia_Warn:AddMessage(string.format(PARANOIA_ALERTCLASSKNOWN, oppFaction, gHostileList[name].class, name), 255, 0, 0, 1, Paranoia_Config.alertPopupdisplayTime); 
          end
        end
        gLastwarnHostilePlayer = GetTime();
      end
    end
  end
  Paranoia:OnUpdate();
  
end

function Paranoia:addNewHostile(name, class, level, source, guild)

  if source == nil then source = 0; end
  
  gHostileList[name] = { time = GetTime() };  --create new player with last activity time
  gHostileList[name].lastComm = GetTime();    --set lastcomm time to now
  gCurHostiles = gCurHostiles + 1;
  
  local posX, posY = GetPlayerMapPosition("player");  --updating positioning
  posX=floor(posX *100);
  posY=floor(posY*100);
  gHostileList[name].x = posX;
  gHostileList[name].y = posY;
  gHostileList[name].zone = GetMinimapZoneText();
  
  if Paranoia_edb.players[GetRealmName()][oppFactionEng][name] ~= nil then --player exists in the database.
    if source ~= 1 and level ~= -1 then --source 1 is mouseover/target, and the player is not skulled, so that means we already know the level and don't need to retrieve it.
      local tempLevel, tempClass;
      tempLevel, tempClass = Paranoia:getPlayerLevelClass(name);
      tempLevel = tonumber(tempLevel)
      if tempLevel == nil then tempLevel = 0; end
      Paranoia:dMsg("Retreived "..name.."'s level from database ("..tempLevel..")... var level="..level.." | type="..type(level));
      if (tempLevel ~= 0) and (tempLevel > level) then level = tempLevel; end
      if (tempClass ~= PARANOIA_UNKNOWN) then class = tempClass; end
      tempClass = nil;
      tempLevel = nil;
    end
  end
  
  if (class ~= PARANOIA_UNKNOWN) then
    gHostileList[name].class = class;
  else
    gHostileList[name].class = PARANOIA_UNKNOWN;
  end

  Paranoia:strLevel(name, level, source);
  
  if Paranoia_edb.players[GetRealmName()][oppFactionEng][name] == nil then
    Paranoia:createNewPlayer(name, Paranoia:numLevel(gHostileList[name].levelDisp), gHostileList[name].class, guild, time(), false);
  end
  
  if Paranoia_Config.enableComm and source < 2 then
    Paranoia:commMsg("DATA", name.."^"..gHostileList[name].class.."^"..gHostileList[name].level.."^"..gHostileList[name].x..",".."^"..gHostileList[name].y, "PARTY");
    if IsInGuild() ~= nil then Paranoia:commMsg("DATA", name.."^"..gHostileList[name].class.."^"..gHostileList[name].level.."^"..gHostileList[name].x..",".."^"..gHostileList[name].y, "GUILD"); end
    gHostileList[name].lastComm = GetTime();
  end
end

function Paranoia:updateHostileList()
  if isParanoiaDisabled then return; end
  -- This function is called every OnUpdate().
  local buttonText, i, displayed;
  local dispName, class;
  local paraWidth = 0;
  displayed = 0;
  i = 1;
  
  table.sort(gHostileList);
  for name,_ in pairs(gHostileList) do
    if (gHostileList[name].time < GetTime() - Paranoia_Config.listTimeout) then
      -- Enemy has reached the time limit for inactivity... remove them from the enemy table.
      gHostileList[name] = nil;
      gCurHostiles = gCurHostiles - 1;
      
    elseif (i <= Paranoia_Config.maxHostiles) then
      -- Enemy is still valid. 
      if not InCombatLockdown() then
        buttonFrame = getglobal("Paranoia_StatusFrame"..i);
        buttonText = getglobal("Paranoia_StatusFrame"..i.."Text");
        button = getglobal("Paranoia_StatusFrame"..i.."Button")
        if buttonText:GetStringWidth() + 17 > paraWidth then 
          paraWidth = buttonText:GetStringWidth() + 17;
        end
        buttonFrame:SetWidth(paraWidth);
        buttonText:SetWidth(paraWidth);
      end

      dispName = name;

      -- Set class variable.
      class = gHostileList[name].class;
      if (not (class)) then
        class = PARANOIA_UNKNOWN;
      end

      if not InCombatLockdown() then 
      buttonFrame:Show(); 
      end
      
      
      if (not InCombatLockdown()) then
        button.nickname = name;
        button.id = name;
      end
      
      -- Apply inactivity alpha.
      textAlpha = 1;
      colorR = 1;
      colorG = 1;
      colorB = 0;
      if (Paranoia_Config.inactivityFade) then
        timeSinceLast = Paranoia:round(GetTime() - gHostileList[name].time,3);
        fadeTicks = timeSinceLast / Paranoia_Config.listTimeout;
        textAlpha = (1 - fadeTicks);
      end
      
      -- Apply class coloring.

      if (strsub(class,1,4) == PARANOIA_WARLOCK_SHORT) then colorR = RAID_CLASS_COLORS["WARLOCK"].r; colorG = RAID_CLASS_COLORS["WARLOCK"].g; colorB = RAID_CLASS_COLORS["WARLOCK"].b;
      elseif (strsub(class,1,4) == PARANOIA_DRUID_SHORT) then colorR = RAID_CLASS_COLORS["DRUID"].r; colorG = RAID_CLASS_COLORS["DRUID"].g; colorB = RAID_CLASS_COLORS["DRUID"].b;
      elseif (strsub(class,1,4) == PARANOIA_HUNTER_SHORT) then colorR = RAID_CLASS_COLORS["HUNTER"].r; colorG = RAID_CLASS_COLORS["HUNTER"].g; colorB = RAID_CLASS_COLORS["HUNTER"].b;
      elseif (strsub(class,1,4) == PARANOIA_MAGE_SHORT) then colorR = RAID_CLASS_COLORS["MAGE"].r; colorG = RAID_CLASS_COLORS["MAGE"].g; colorB = RAID_CLASS_COLORS["MAGE"].b;
      elseif (strsub(class,1,4) == PARANOIA_PALADIN_SHORT) then colorR = RAID_CLASS_COLORS["PALADIN"].r; colorG = RAID_CLASS_COLORS["PALADIN"].g; colorB = RAID_CLASS_COLORS["PALADIN"].b;
      elseif (strsub(class,1,4) == PARANOIA_PRIEST_SHORT) then colorR = RAID_CLASS_COLORS["PRIEST"].r; colorG = RAID_CLASS_COLORS["PRIEST"].g; colorB = RAID_CLASS_COLORS["PRIEST"].b;
      elseif (strsub(class,1,4) == PARANOIA_ROGUE_SHORT) then colorR = RAID_CLASS_COLORS["ROGUE"].r; colorG = RAID_CLASS_COLORS["ROGUE"].g; colorB = RAID_CLASS_COLORS["ROGUE"].b;
      elseif (strsub(class,1,4) == PARANOIA_SHAMAN_SHORT) then colorR = RAID_CLASS_COLORS["SHAMAN"].r; colorG = RAID_CLASS_COLORS["SHAMAN"].g; colorB = RAID_CLASS_COLORS["SHAMAN"].b;
      elseif (strsub(class,1,4) == PARANOIA_WARRIOR_SHORT) then colorR = RAID_CLASS_COLORS["WARRIOR"].r; colorG = RAID_CLASS_COLORS["WARRIOR"].g; colorB = RAID_CLASS_COLORS["WARRIOR"].b;
      elseif (strsub(class,1,4) == PARANOIA_DEATHKNIGHT_SHORT) then colorR = RAID_CLASS_COLORS["DEATHKNIGHT"].r; colorG = RAID_CLASS_COLORS["DEATHKNIGHT"].g; colorB = RAID_CLASS_COLORS["DEATHKNIGHT"].b;
      else colorR = 0.6; colorG = 0.6; colorB =0.6; end
            
      if (not InCombatLockdown()) then
        buttonText:SetText(dispName.." ("..class.."/"..gHostileList[name].levelDisp..")");
        buttonText:SetTextColor(colorR, colorG, colorB, textAlpha);
      end
      
      displayed = displayed + 1;
      i = i + 1;
    end
    
  end

  if not InCombatLockdown() then 
  Paranoia_Main:SetWidth(paraWidth); 
  end
  
  -- Clear empty buttons.
  while (i <= 15) do        --15 is the maximum number of lines allowed by the paranoia code
    if (not InCombatLockdown()) then
      buttonFrame = getglobal("Paranoia_StatusFrame"..i);
      buttonText = getglobal("Paranoia_StatusFrame"..i.."Text");
      button = getglobal("Paranoia_StatusFrame"..i.."Button");
      if i > 2 then buttonFrame:Hide(); end
      if not InCombatLockdown() then 
        button.nickname = "emptyButton"..i;
        button.id = "emptyid"..i; 
      end
      buttonText:SetText("");
      buttonText:SetTextColor(0.5, 0.5, 0.5);
    end
    i = i + 1;
  end
  
  -- Display No players detected message if all buttons are empty
  if (displayed == 0) then
    displayed = 2;
    if (not InCombatLockdown()) then
      if string.find(PARANOIA_NODETECTL1, "%s", 1, true) ~= nil then  --(since %s is a regex character we must specify plain matching)
        Paranoia_StatusFrame1Text:SetText(string.format(PARANOIA_NODETECTL1, oppFaction));
        Paranoia_StatusFrame2Text:SetText(PARANOIA_NODETECTL2);
      else
        Paranoia_StatusFrame1Text:SetText(PARANOIA_NODETECTL1);
        Paranoia_StatusFrame2Text:SetText(string.format(PARANOIA_NODETECTL2, oppFaction));
      end
      Paranoia_StatusFrame1Text:SetTextColor(0.5, 0.5, 0.5);
      Paranoia_StatusFrame2Text:SetTextColor(0.5, 0.5, 0.5);
      local strWidth = Paranoia_StatusFrame1Text:GetStringWidth();
      if strWidth < Paranoia_StatusFrame2Text:GetStringWidth() then strWidth = Paranoia_StatusFrame2Text:GetStringWidth(); end
      Paranoia_Main:SetWidth(strWidth + 17);
    end
  end
  
  -- Remove ignored/nothostile players from ignore list if three hours has passed.
  for name,_ in pairs(gNotHostileList) do
    if (gNotHostileList[name].time < GetTime() - gNotHostileList[name].timeout) then		-- 3 hours
      gNotHostileList[name] = nil;
      gCurNotHostiles = gCurNotHostiles - 1;
    end
  end

  --Change frame height and width according to buttons
  
  if (displayed < 2) then
    displayed = 2;
  end

  if (not InCombatLockdown()) then 
    Paranoia_Main:SetHeight(10 + displayed*18); 
  end


end

--===================================================================================================================--
--  8) WarnFrame Functions
--===================================================================================================================--

function Paranoia:WarnFrameShow()
  -- "Interface/DialogFrame/UI-DialogBox-Background"
  -- "Interface/Tooltips/UI-Tooltip-Background"
  isConfigOpen = true;
  Paranoia_Warn:Clear();
  Paranoia_Warn:SetBackdrop({bgFile = "Interface/DialogFrame/UI-DialogBox-Background"});
  Paranoia_Warn:EnableMouse(true);
  Paranoia_Warn:Show();
  Paranoia_Warn:SetTimeVisible(500);
  Paranoia_Warn:AddMessage(PARANOIA_ALERTMOVEABLE, 0, 1, 0)
  Paranoia_Main:RegisterForDrag("LeftButton")
end

function Paranoia:WarnFrameTest()
  Paranoia_Warn:Clear();
  Paranoia_Warn:SetTimeVisible(Paranoia_Config.alertPopupdisplayTime);
  Paranoia_Warn:SetFadeDuration(Paranoia_Config.alertPopupfadeTime);
  Paranoia_Warn:AddMessage(string.format(PARANOIA_ALERTTEST, oppFaction), 255, 0, 0 ,100)
end

function Paranoia:WarnFrameHide(cancel)
  isConfigOpen = false;
  Paranoia_Warn:Clear();
  Paranoia_Warn:SetBackdrop(nil);
  Paranoia_Warn:EnableMouse(false);
  Paranoia_Warn:SetTimeVisible(Paranoia_Config.alertPopupdisplayTime);
  Paranoia_Warn:SetFadeDuration(Paranoia_Config.alertPopupfadeTime);
  Paranoia_Main:RegisterForDrag("")
  if cancel then  --replace our modified Paranoia_Config table with our backup copy.
    if configCopy == nil then return; end
    Paranoia_Config = CopyTable(configCopy);
    Paranoia:setLock(configCopy["listLocked"]);
    Paranoia_Main:SetScale(configCopy["listScale"]);
    if ( not (Paranoia_Main:GetAlpha() == 0 ) ) then Paranoia_Main:SetAlpha(configCopy["frameOpacity"]); end
    Paranoia_Main:SetBackdropBorderColor(255, 255, 255, configCopy["borderOpacity"]);
    Paranoia_Warn:SetAlpha(configCopy["alertPopupAlpha"]);
    Paranoia:UpdateMinimapVisibility();
    end
  configCopy = nil;
end

--===================================================================================================================--
--  9) Configuration Frame Functions
--===================================================================================================================--

function Paranoia:OptionCheckBoxClicked(self, option)

  Paranoia_Config[option] = Paranoia:ToBoolean(self:GetChecked());
  
end

function Paranoia:LockedCheckBoxClicked(self)

  if (Paranoia:ToBoolean(self:GetChecked())) then
    Paranoia:setLock(true);
    Paranoia_Config['listLocked'] = true;
  else
    Paranoia:setLock(false);
    Paranoia_Config['listLocked'] = false;
  end
  
end

function Paranoia:OptionCheckBoxInit(self, option)

  self:SetChecked(Paranoia:ToBoolean(Paranoia_Config[option]));

end

function Paranoia:SliderMoved(self, option)

  Paranoia_Config[option] = self:GetValue();

end

function Paranoia:SliderInit(self, option)

  self:SetValue(Paranoia_Config[option]);

end

function Paranoia:ShowConfig()
  --[[
  Upcoming 3.0 Changes - Concise List - 07/18/2008 02:01:11 PM PDT
  
  Interface Options
    * InterfaceOptionsFrame_OpenToPage has been renamed InterfaceOptionsFrame_OpenToCategory !!!
  
  Lol, break a bunch of addons' config frames and have users mass post comments in confusion, 
  but that's okay since the function has been renamed to be a little more indicative of what it does!
  It was awfully confusing as OpenToPage. Good job, Blizzard. :P
  ]]--
  
  configCopy = CopyTable(Paranoia_Config);
  InterfaceOptionsFrame_OpenToCategory(PARANOIA_TABGENERAL);
  
end

--===================================================================================================================--
--  10) Dropdown List Functions
--===================================================================================================================--

function Paranoia:ShowEDD(eName, bName)
  if (string.find(eName, "emptyButton") ~= nil) then 
    Paranoia:ShowConfig();
    return; 
  end
  channelid, channelname = GetChannelName(PARANOIA_LOCALDEFCHAN.." - "..GetZoneText())

  Dewdrop:Open(Paranoia_Main,
    "children", function()
      Dewdrop:AddLine("text", tostring(eName), "isTitle", true)
      Dewdrop:AddLine("text", PARANOIA_ANNOUNCESAY, "func", function() Paranoia:AnnounceHostile(eName, "SAY"); end)
      Dewdrop:AddLine("text", PARANOIA_ANNOUNCEYELL, "func", function() Paranoia:AnnounceHostile(eName, "YELL"); end)
      if (GetNumPartyMembers() ~= 0) then Dewdrop:AddLine("text", PARANOIA_ANNOUNCEPARTY, "func", function() Paranoia:AnnounceHostile(eName, "PARTY"); end); end
      if (IsInGuild() ~= nil) then Dewdrop:AddLine("text", PARANOIA_ANNOUNCEGUILD, "func", function() Paranoia:AnnounceHostile(eName, "GUILD"); end); end
      if (GetNumRaidMembers() ~= 0 and not Paranoia:IsPlayerInBattlefield()) then Dewdrop:AddLine("text", PARANOIA_ANNOUNCERAID, "func", function() Paranoia:AnnounceHostile(eName, "RAID"); end); end
      if (Paranoia:IsPlayerInBattlefield()) then Dewdrop:AddLine("text", PARANOIA_ANNOUNCEBG, "func", function() Paranoia:AnnounceHostile(eName, "BATTLEGROUND"); end); end
      if (channelid ~= nil and channelid ~= 0) then Dewdrop:AddLine("text", string.format(PARANOIA_ANNOUNCELOCALDEF, channelid), "func", function() Paranoia:AnnounceHostile(eName, "CHANNEL"); end); end
      if (debugEnabled) then Dewdrop:AddLine("text", "|cFFFF7D0ApDebug: Print test announce to chat frame|r", "func", function() Paranoia:AnnounceHostile(eName, "DEBUG"); end); end
      if not Paranoia:IsPlayerInFFA() then
        Dewdrop:AddSeparator();
        Dewdrop:AddLine("text", string.format(PARANOIA_ANNOUNCEIGNORE, eName), "func", function() Dewdrop:Close(); Paranoia:AddToIgnore(eName); end)
      end
      Dewdrop:AddSeparator();
      Dewdrop:AddLine("text", PARANOIA_ANNOUNCECONFIG, "func", function() Paranoia:ShowConfig(); Dewdrop:Close(); end)
    end)  
    
end

function Paranoia:AddToIgnore(name)
  gHostileList[name] = nil; 
  gCurHostiles = gCurHostiles - 1; 
  Paranoia:addNotHostile(name, (60*60*3));
  Paranoia:Msg(string.format(PARANOIA_CHATIGNORED, name)); 
end

function Paranoia:AnnounceHostile(name, channel)
  local classDisp, levelDisp;
  local messageText = Paranoia_Config.customMsg; -- %f = opposite faction, %n = name, %l = level, %c = class, %z = zone, %x and %y = x/y coords respectively
  
  if (channel == "TEST") then
    local posX, posY = GetPlayerMapPosition("player");
    posX=floor(posX *100);
    posY=floor(posY*100);
    messageText = string.gsub(messageText, '%%f', oppFaction);
    messageText = string.gsub(messageText, '%%n', PARANOIA_ANNOUNCE_TESTNAME);
    messageText = string.gsub(messageText, '%%l', "80");
    messageText = string.gsub(messageText, '%%c', PARANOIA_MAGE);
    messageText = string.gsub(messageText, '%%z', GetMinimapZoneText());
    messageText = string.gsub(messageText, '%%x', posX);
    messageText = string.gsub(messageText, '%%y', posY);  
    Paranoia:Msg(PARANOIA_CHATTESTANNOUNCE); 
    DEFAULT_CHAT_FRAME:AddMessage(string.format(PARANOIA_CHATFAKEANNOUNCE, UnitName("player"), messageText)); 
    return;
  end

  if (not gHostileList[name].class or gHostileList[name].class == PARANOIA_UNKNOWN) then
    classDisp = PARANOIA_UNKNOWN;
  else
    classDisp = gHostileList[name].class;
  end
  
  --replace custom message
  messageText = string.gsub(messageText, '%%f', oppFaction);
  messageText = string.gsub(messageText, '%%n', name);
  messageText = string.gsub(messageText, '%%l', gHostileList[name].levelDisp);
  messageText = string.gsub(messageText, '%%c', classDisp);
  messageText = string.gsub(messageText, '%%z', gHostileList[name].zone);
  messageText = string.gsub(messageText, '%%x', gHostileList[name].x);
  messageText = string.gsub(messageText, '%%y', gHostileList[name].y);
  
  if (channel == "DEBUG") then
    Paranoia:dMsg("[Paranoia] "..messageText);
    return;
  end
  
  SendChatMessage("[Paranoia] "..messageText, channel, nil, channelid);
  Dewdrop:Close()
end

--===================================================================================================================--
--  11) Minimap Button Functions (using Gello's minimap button template from the WoWI forums)
--===================================================================================================================--

function Paranoia:MinimapButton_Reposition()
	Paranoia_MinimapButton:SetPoint("TOPLEFT","Minimap","TOPLEFT",52-(80*cos(Paranoia_Config.minimapPos)),(80*sin(Paranoia_Config.minimapPos))-52)
end


function Paranoia_MinimapButtonDraggingFrame_OnUpdate()

	local xpos,ypos = GetCursorPosition()
	local xmin,ymin = Minimap:GetLeft(), Minimap:GetBottom()

	xpos = xmin-xpos/UIParent:GetScale()+70 -- get coordinates as differences from the center of the minimap
	ypos = ypos/UIParent:GetScale()-ymin-70
  
	Paranoia_Config.minimapPos = math.deg(math.atan2(ypos,xpos)) -- save the degrees we are relative to the minimap center
	Paranoia:MinimapButton_Reposition() -- move the button
end

-- Put your code that you want on a minimap button click here.  arg1="LeftButton", "RightButton", etc
function Paranoia_MinimapButton_OnClick()
	if arg1 == "LeftButton" then
    if Paranoia_Config.enabled == false then
      Paranoia_Config.enabled = true;
      Paranoia:OnUpdate();
      Paranoia:Msg(PARANOIA_CHATENABLED);
    else
      Paranoia_Config.enabled = false;
      Paranoia:Msg(PARANOIA_CHATDISABLED);
    end
  else
    Paranoia:ShowConfig();
  end
end

function Paranoia:UpdateMinimapVisibility()
  
  if Paranoia_Config.hideMinimap then
    Paranoia_MinimapButton:Hide();
  else
    Paranoia_MinimapButton:Show();
  end
  
end

Paranoia_html = {};



