--===========================================================================--
----------------------------  LootTracker by PNB  ----------------------------
--===========================================================================--
-- LootTracker.lua
--
-- Primary entry points to the AddOn
--===========================================================================--

--===========================================================================--
-- Constants
--===========================================================================--

LT_Version              = "1.5.1";

LT_MinTooltipMode       = 1;
LT_MaxTooltipMode       = 3;

LT_QualityColors = {
    ["-2"] = {r=0.00, g=0.88, b=1.00},
    ["-1"] = {r=0.89, g=0.82, b=0.39},
    ["0"]  = {r=0.50, g=0.50, b=0.50},
    ["1"]  = {r=1.00, g=1.00, b=1.00},
    ["2"]  = {r=0.12, g=1.00, b=0.00},
    ["3"]  = {r=0.00, g=0.44, b=0.87},
    ["4"]  = {r=0.64, g=0.21, b=0.93},
    ["5"]  = {r=1.00, g=1.00, b=1.00}
};

LT_MinQuality = -2
LT_MaxQuality = 5;

LT_MoneyColors = {
    [0] = {r=0.89, g=0.82, b=0.39},
    [1] = {r=0.71, g=0.71, b=0.71},
    [2] = {r=0.66, g=0.33, b=0.11}
};

LT_White = {r=1.00, g=1.00, b=1.00};


--===========================================================================--
-- Saved Variables
-- These are the values that will be persisted from session to session.
--===========================================================================--

------------------------------------------------------------------------------
-- LT_Data holds all data on items, kills, and players for the current session
-- and for any other saved sessions.
-- TODO: Make this also forked per-character
------------------------------------------------------------------------------
LT_Data = { }
-- ["Realm - Player"]
--   ["SessionName"]
--     Items
--       ["ItemName"]
--         Name
--         Quality
--         Value
--         Zones (list)
--         TimesLooted (list)
--         Recipients (list)
--         Sources (list)
--     Kills
--       ["KillName"]
--         Name
--         Level
--         TimesKilled (list)
--         Drops (list)
--     Players
--       ["PlayerName"]
--         Name
--         Level
--         TimesKilled (list)
--         LootReceived (list)
--         Gear (list)

------------------------------------------------------------------------------
-- LT_Settings holds configuration values.
------------------------------------------------------------------------------
LT_Settings = { }
-- ["Realm - Player"]
--   CurrentSession
--   DebugLevel
--   JustMyLoot
--   ChatColor
--   QualityThreshold
--   TooltipShowItems
--   TooltipShowKills
--   TooltipShowPlayers
--   IgnoreCraftLoot
--   IgnoreQuestLoot
--   IgnoreVendorLoot


--===========================================================================--
-- Globals
-- These values are global, but not saved when the session ends.
--===========================================================================--

LT_RealmAndPlayer       = "";
LT_ChangeListeners      = nil;

------------------------------------------------------------------------------
-- LT_PendingLoot holds onto any items we've seen in a loot window, but 
-- haven't yet linked to a recipient.
------------------------------------------------------------------------------
LT_PendingLoot = {};
-- ["ItemName"]
--   Name
--   Source
--   Quantity (sometimes)
--   Value (sometimes)

------------------------------------------------------------------------------
-- LT_PendingLootBySlot holds the same information as LT_PendingLoot, but its
-- indexed by the original loot slot index, and the table is cleared as soon
-- as the loot window is closed.  This table is used to handle the LootRemoved
-- event.
------------------------------------------------------------------------------
LT_PendingLootBySlot = {};
-- ["Slot"]


------------------------------------------------------------------------------
-- LT_PendingTarget holds onto any units we've targeted.
------------------------------------------------------------------------------
LT_PendingTargets = {};
-- ["TargetName"]
--   Class
--   Level


--===========================================================================--
-- Frame Hooks
-- These are the functions linked from LootTracker.xml
--===========================================================================--

------------------------------------------------------------------------------
-- OnLoad
-- Invoked when the addon is loaded.  Gives us a chance to register for any 
-- events we might care about.
------------------------------------------------------------------------------

function LootTracker_OnLoad()

    this:RegisterEvent("VARIABLES_LOADED");
    this:RegisterEvent("ADDON_LOADED");
    
    this:RegisterEvent("CHAT_MSG_LOOT");
    this:RegisterEvent("CHAT_MSG_MONEY");
    this:RegisterEvent("CHAT_MSG_COMBAT_FRIENDLY_DEATH");
    this:RegisterEvent("CHAT_MSG_COMBAT_HOSTILE_DEATH");
    this:RegisterEvent("CHAT_MSG_SYSTEM");
    this:RegisterEvent("CHAT_MSG_COMBAT_FACTION_CHANGE");
    this:RegisterEvent("CHAT_MSG_COMBAT_HONOR_GAIN");
    this:RegisterEvent("CHAT_MSG_COMBAT_XP_GAIN");
    this:RegisterEvent("CHAT_MSG_SKILL");

    this:RegisterEvent("PLAYER_TARGET_CHANGED");
    
    this:RegisterEvent("LOOT_OPENED");
    this:RegisterEvent("LOOT_CLOSED");
    this:RegisterEvent("LOOT_SLOT_CLEARED");

end


------------------------------------------------------------------------------
-- OnEvent
-- Invoked when any of our registered events are fired
------------------------------------------------------------------------------

function LootTracker_OnEvent(event, arg1)

    if (event == "VARIABLES_LOADED") then
        LT_OnStartup();
        return;
    end
    
    if (event == "ADDON_LOADED") then
        return;
    end

    if (event == "CHAT_MSG_LOOT") then
        LT_OnLootMessage(arg1);
        return;
    end
    
    if (event == "CHAT_MSG_MONEY") then
        LT_OnMoneyMessage(arg1);
        return;
    end
    
    if (event == "CHAT_MSG_COMBAT_FRIENDLY_DEATH") then
        LT_OnFriendlyDeathMessage(arg1);
        return;
    end
    
    if (event == "CHAT_MSG_COMBAT_HOSTILE_DEATH") then
        LT_OnHostileDeathMessage(arg1);
        return;
    end
    
    if (event == "CHAT_MSG_SYSTEM") then
        LT_OnSystemMessage(arg1);
        return;
    end
    
    if (event == "CHAT_MSG_COMBAT_FACTION_CHANGE") then
        LT_OnFactionChange(arg1);
        return;
    end
    
    if (event == "CHAT_MSG_COMBAT_HONOR_GAIN") then
        LT_OnGainHonor(arg1);
        return;
    end
    
    if (event == "CHAT_MSG_COMBAT_XP_GAIN") then
        LT_OnGainExperience(arg1);
        return;
    end
    
    if (event == "CHAT_MSG_SKILL") then
        LT_OnSkillMessage(arg1);
        return;
    end
    
    if (event == "LOOT_OPENED") then
        LT_OnLootBegin();
        return;        
    end
    
    if (event == "LOOT_CLOSED") then
        LT_OnLootEnd();
        return;
    end
    
    if (event == "LOOT_SLOT_CLEARED") then
        LT_OnLootSlotCleared(arg1);
        return;
    end
    
    if (event == "PLAYER_TARGET_CHANGED") then
        LT_OnTargetChanged();
        return;
    end
    
    LT_DebugMessage(2, "Unexpected event: " .. event);

end



--===========================================================================--
-- Event Handlers
--===========================================================================--

------------------------------------------------------------------------------
-- OnStartup
------------------------------------------------------------------------------

function LT_OnStartup()

    -- Hook into console commands
    SlashCmdList["LT_"] = LT_OnSlashCommand;
    
    
    -- Get realm and player names
    local realm   =  GetCVar("realmName");
    local player  =  UnitName("player");
    LT_RealmAndPlayer = realm .. " - " .. player;

    
    -- Initialize the LT_Settings structure
    local settings = LT_GetSettings();
    
    if (settings.CurrentSession == nil) then
        settings.CurrentSession = LT_DEFAULT_SESSIONNAME;
    end
    
    if (settings.DebugLevel == nil) then
        settings.DebugLevel = 0;
    end
    
    if (settings.JustMyLoot == nil) then
        settings.JustMyLoot = false;
    end
    
    if (settings.ChatColor == nil) then
        settings.ChatColor = {r=0.78,g=0.55,b=1.0};
    else
        -- Convert from 0-255 range to 0-1 range
        if (settings.ChatColor.r > 1.0) then
            settings.ChatColor.r = settings.ChatColor.r / 255;
        end
        if (settings.ChatColor.g > 1.0) then
            settings.ChatColor.g = settings.ChatColor.g / 255;
        end
        if (settings.ChatColor.b > 1.0) then
            settings.ChatColor.b = settings.ChatColor.b / 255;
        end
    end
    
    if (settings.QualityThreshold == nil) then
        settings.QualityThreshold = 2;
    end
    
    if (settings.TooltipMode == nil) then
        settings.TooltipMode = LT_MinTooltipMode;
    end
    if (settings.TooltipShowItems == nil) then
        settings.TooltipShowItems = true;
    end
    if (settings.TooltipShowKills == nil) then
        settings.TooltipShowKills = true;
    end
    if (settings.TooltipShowPlayers == nil) then
        settings.TooltipShowPlayers = false;
    end
    
    if (settings.IgnoreCraftLoot == nil) then
        settings.IgnoreCraftLoot = true;
    end
    if (settings.IgnoreQuestLoot == nil) then
        settings.IgnoreQuestLoot = false;
    end
    if (settings.IgnoreVendorLoot == nil) then
        settings.IgnoreVendorLoot = true;
    end

    -- Initialize the LT_Data structure
    if (LT_Data == nil) then
        LT_Data = {};
    end

    -- Check the version    
    LT_ValidateSession();
    
    
    -- Initialize the settings UI
    -- TODO: Does this really do anything?
    UIPanelWindows["LT_SettingsUI"] = {area = "center", pushable = 0};
    
    
    -- Output a "We've loaded" message
    -- TODO: Why isn't this appropriately colored?
    LT_Message(string.format(LT_STARTUP, LT_Version));
    
end


------------------------------------------------------------------------------
-- OnLootMessage
-- There's been a loot message in chat - process it.
------------------------------------------------------------------------------

function LT_OnLootMessage(text)

    LT_DebugMessage(4, "Raw Loot Message: " .. text);
    local settings = LT_GetSettings();

    -- SomeOtherPlayer receives loot: [Heavy Leather]
    local beginMatch, endMatch, recipient, itemLink = string.find(text, LT_LOOT_RECEIVED);
    
    if (beginMatch) then
    
        LT_OnParsedLootMessage(recipient, itemLink);

        return;
    end
    
    -- You receive loot: [Heavy Leather]
    local beginMatch, endMatch, itemLink = string.find(text, LT_LOOT_RECEIVED_YOU);
    
    if (beginMatch) then
    
        LT_OnParsedLootMessage(LT_YOU, itemLink);

        return;
    end
    
    
    -- You receive item: [Warlords Deck]
    local beginMatch, endMatch, itemLink = string.find(text, LT_LOOT_ITEM);
    
    if (beginMatch) then
    
        if (not settings.IgnoreVendorLoot) then
    
            -- This match covers both bought items and combined items (like Warlords Deck).
            -- We need the setting to disable it for people who don't want to include 
            -- purchased ammo etc., but it'd be nice to be able to sort out true vendor
            -- items from quest created items.
        
            local item, player = LT_OnParsedLootMessage(LT_YOU, itemLink);
            
            -- Mark the source as "Vendor"
            LT_AddSource(item, LT_SOURCE_VENDOR, 1);
            
        else
            LT_DebugMessage(2, "Ignoring vendor loot.");
        end

        return;
    end
    
    -- "You create: [Copper Bar]"   
    local beginMatch, endMatch, itemLink = string.find(text, LT_LOOT_CREATED);
    if (beginMatch) then
    
        if (not settings.IgnoreCraftLoot) then
    
            -- Keep processing the loot message
            local item, player = LT_OnParsedLootMessage(LT_YOU, itemLink);
            
            -- Mark the source as "Craft"
            LT_AddSource(item, LT_SOURCE_CRAFT, 1);

        else
            LT_DebugMessage(2, "Ignoring craft loot.");
        end
    
        return;
    end
    
    -- "You won [Meaty Bat Wing]"
    local beginMatch, endMatch, recipient, itemLink = string.find(text, LT_LOOT_WON);
    if (beginMatch) then

        -- TODO: Do something with this message
        LT_DebugMessage(1, string.format("%s won %s", recipient, itemLink));

    end
    
    LT_DebugMessage(2, "Loot message did not match pattern: " .. text);

end


------------------------------------------------------------------------------
-- OnParsedLootMessage
-- Process a loot message.
------------------------------------------------------------------------------

function LT_OnParsedLootMessage(recipientName, itemLink)

    -- This shouldn't happen, but validate that we got our out parameters.
    if (recipientName == nil) then
        LT_DebugMessage(2, "Recipient was nil");
        return;
    end
    if (itemLink == nil) then
        LT_DebugMessage(2, "Item link was nil");
        return;
    end

    -- Map "You" to a player name
    if (recipientName == LT_YOU) then
        recipientName = UnitName("player");
    else
        -- If we're configured to only consider local loot, and this isn't
        -- going to us, bail now.
        if (LT_IsPlayerSolo()) then
            LT_DebugMessage(2, "Ignoring loot going to another player.");
            return;
        end
    end
    
    -- Get the Id from the link
    local itemId = LT_ExtractItemIDFromChatLink(itemLink);
    
    LT_DebugMessage(4, "Extracted item id: " .. itemId);
    
    local item, quantity = LT_CreateItem(itemId);
    local player         = LT_CreatePlayer(recipientName);
    
    -- Bail out if we were unable to create either an item or player object.
    if (item == nil) then
        LT_DebugMessage(2, "Error creating item object");
        return;
    end
    if (player == nil) then
        LT_DebugMessage(2, "Error creating player object");
        return;
    end
    
    for i = 1, quantity do
        LT_AddLootToPlayer(item, player);
    end
    
    return item, player;

end


------------------------------------------------------------------------------
-- OnFriendlyDeathMessage
-- There's been a death message in chat - process it.
------------------------------------------------------------------------------

function LT_OnFriendlyDeathMessage(text)

    LT_DebugMessage(4, "Raw (Friendly) Death Message: " .. text);
    
    local beginMatch, endMatch, deceased = string.find(text, LT_FRIENDLY_DEATH);

    if (beginMatch) then
        -- This shouldn't happen, but validate that we got our out parameters.
        if (deceased == nil) then
            LT_DebugMessage(2, "Deceased was nil");
            return;
        end

        local unitId = nil;
        
        -- Map "You" to a player name
        if (deceased == LT_YOU) then
            deceased = UnitName("player");
            unitId = "player";
        else
            unitId = LT_GetPlayerUnitID(deceased);
        end
        
        if (unitId == nil) then
            LT_DebugMessage(2, "Unable to get UnitID for player: " .. deceased);
            return;
        end
        
        -- We don't want to bother logging non-party deaths.
        if (not UnitInParty(unitId) and not UnitInRaid(unitId)) then
            LT_DebugMessage(2, "Ignoring death of non-party character: " .. deceased);
        end
        
        -- Create an entry for this player if there isn't one already.
        local player = LT_CreatePlayer(deceased);

        local timeOfDeath = date();
            
        -- Log the time of death.
        if (player.TimesKilled == nil) then
            player.TimesKilled = { timeOfDeath };
        else
            table.insert(player.TimesKilled, timeOfDeath);
        end
        
        LT_DebugMessage(2, "Player died: " .. player.Name);
        
        return;
    else
        LT_DebugMessage(3, "Friendly death message did not match pattern: " .. text);
    end

end


------------------------------------------------------------------------------
-- OnHostileDeathMessage
-- There's been a death message in chat - process it.
------------------------------------------------------------------------------

function LT_OnHostileDeathMessage(text)

    LT_DebugMessage(4, "Raw (Hostile) Death Message: " .. text);
    
    local beginMatch, endMatch, deceased = string.find(text, LT_HOSTILE_DEATH);

    if (beginMatch) then
        -- This shouldn't happen, but validate that we got our out parameters.
        if (deceased == nil) then
            LT_DebugMessage(2, "Deceased was nil");
            return;
        end
        
        -- TODO: If this kill did not come from our group, don't count it.
        --       Use tapped state to detect this... somehow?
        
        -- Log this kill
        kill = LT_CreateKill(deceased);
        
        return;
    else
        LT_DebugMessage(3, "Hostile death message did not match pattern: " .. text);
    end

end


------------------------------------------------------------------------------
-- OnSystemMessage
-- There's been a system message in chat - process it.
------------------------------------------------------------------------------

function LT_OnSystemMessage(text)

    LT_DebugMessage(4, "Raw System Message: " .. text);

    -- Received item: [Super Duper Quest Reward]
    local beginMatch, endMatch, itemLink = string.find(text, LT_RECEIVED_ITEM);
    if (beginMatch) then
    
        local settings = LT_GetSettings();
        if (not settings.IgnoreQuestLoot) then
    
            -- This shouldn't happen, but validate that we got our out parameters.
            if (itemLink == nil) then
                LT_DebugMessage(2, "Link was nil");
                return;
            end
            
            -- Keep processing the loot message
            local item, player = LT_OnParsedLootMessage(LT_YOU, itemLink);
            
            -- Mark the source as "Quest"
            LT_AddSource(item, LT_SOURCE_QUEST, 1);

        else
            LT_DebugMessage(2, "Ignoring quest loot.");
        end
    
        return;
    end
    
    -- Received 7 Silver.
    local beginMatch, endMatch, amount = string.find(text, LT_RECEIVED_MONEY);
    if (beginMatch) then
    
        local settings = LT_GetSettings();
        if (not settings.IgnoreQuestLoot) then
        
            LT_DebugMessage(2, "Quest money reward: " .. tostring(amount));
    
            local item = LT_OnMoneyLootText(amount, false);

            -- Mark the source as "Quest"
            LT_AddSource(item, LT_SOURCE_QUEST, 1);
            
        else
            LT_DebugMessage(2, "Ignoring quest money reward.");
        end
    
        return;
    end
    
    -- Discovered Ratchet: 105 experience gained
    local beginMatch, endMatch, amount = string.find(text, LT_EXPERIENCE_GAINED_EXPLORE);
    if (beginMatch) then
    
        LT_OnGainExperienceAmount(tonumber(amount), LT_SOURCE_EXPLORATION);
        
        return;

    end
    
    -- Experience gained: 1950
    local beginMatch, endMatch, amount = string.find(text, LT_EXPERIENCE_GAINED_QUEST);
    if (beginMatch) then
    
        LT_OnGainExperienceAmount(tonumber(amount), LT_SOURCE_QUEST);
        
        return;

    end
    
    LT_DebugMessage(4, "System message did not match pattern: " .. text);

end


------------------------------------------------------------------------------
-- OnFactionChange
-- There's been a faction message in chat - process it.
------------------------------------------------------------------------------

function LT_OnFactionChange(text)

    LT_DebugMessage(4, "Raw faction change: " .. text);

    -- Your reputation with Timbermaw Hold has very slightly increased (5 reputation gained)
    local beginMatch, endMatch, faction = string.find(text, LT_REPUTATION_FACTION);
    if (beginMatch) then
    
        local beginMatch, endMatch, amount = string.find(text, LT_REPUTATION_GAINED);
        if (beginMatch) then
        
            amount = tonumber(amount);
            
            LT_DebugMessage(2, string.format("Gained %d %s reputation", amount, faction));
            
            LT_OnReputationChange(faction, amount)
            
            return;
        end
    
        local beginMatch, endMatch, amount = string.find(text, LT_REPUTATION_LOST);
        if (beginMatch) then
        
            amount = tonumber(amount);
        
            LT_DebugMessage(2, string.format("Lost %d %s reputation", amount, faction));
            
            LT_OnReputationChange(faction, -amount)
        
        end
        
    end
    
    -- You are now Neutral with Booty Bay
    
    LT_DebugMessage(2, "Faction message did not match pattern: " .. text);

end


------------------------------------------------------------------------------
-- OnGainHonor
-- There's been an honor message in chat - process it.
------------------------------------------------------------------------------

function LT_OnGainHonor(text)

    LT_DebugMessage(4, "Raw honor change: " .. text);
    
    -- Rolbek dies, honorable kill Rank: Knight (Estimated Honor Points: 32)
    local beginMatch, endMatch, amount = string.find(text, LT_HONOR_GAINED_KILL);
    if (beginMatch) then
    
        LT_OnGainHonorAmount(tonumber(amount), LT_SOURCE_KILL);
        return;
    end
    
    -- You have been awarded 198 honor points.
    local beginMatch, endMatch, amount = string.find(text, LT_HONOR_GAINED_AWARD);
    if (beginMatch) then
    
        LT_OnGainHonorAmount(tonumber(amount), nil);
        return;
    end
    
    LT_DebugMessage(1, "Honor message did not match pattern: " .. text);

end


------------------------------------------------------------------------------
-- OnGainExperience
-- There's been an experience message in chat - process it.
------------------------------------------------------------------------------

function LT_OnGainExperience(text)

    LT_DebugMessage(4, "Raw experience change: " .. text);
    
    -- Greater Plainstrider dies, you gain 144 experience. (+72 Rested bonus)
    local beginMatch, endMatch, amount = string.find(text, LT_EXPERIENCE_GAINED_KILL);
    if (beginMatch) then
    
        LT_OnGainExperienceAmount(tonumber(amount), LT_SOURCE_KILL);
        
        return;

    end
    
    -- You gain 440 experience
    local beginMatch, endMatch, amount = string.find(text, LT_EXPERIENCE_GAINED);
    if (beginMatch) then
    
        -- Ignore these and instead use the system message
        LT_DebugMessage(2, "Ignoring duplicate experience message for " .. amount);
        return;

    end
        
    LT_DebugMessage(1, "Experience message did not match pattern: " .. text);

end


------------------------------------------------------------------------------
-- OnSkillMessage
-- There's been an skill message in chat - process it.
------------------------------------------------------------------------------

function LT_OnSkillMessage(text)

    LT_DebugMessage(4, "Raw skill message: " .. text);
    
    -- Your skill in Staves has increased to 64
    local beginMatch, endMatch, skill, value = string.find(text, LT_SKILL_GAINED);
    if (beginMatch) then
    
        value = tonumber(value);
        
        -- We're assuming that skill changes always come in units of one.
        -- If this isn't true we'll need to do more work and remember the old
        -- skill value.
        local amount = 1;
        
        LT_DebugMessage(1, string.format("Skill change: %s, %d", skill, value));
        
        -- Create a fake item entry to represent the experience
        local rawItem = {};
        rawItem.Quality = -2;
        rawItem.Name = string.format(LT_SKILL_TYPE, skill);
        rawItem.Class = LT_SKILL;
        rawItem.SubClass = skill;

        -- Create the real item entry
        local item = LT_CreateItemFromRaw(rawItem, nil);

        if (item ~= nil) then

            local recipient = UnitName("player");

            -- Add the receiving player to the item's Recipients list
            if (item.Recipients == nil) then
                item.Recipients = { };
            end
            LT_AddCountEntry(item.Recipients, recipient, amount);

            return item;

        end
    
        return;
    end
    
    LT_DebugMessage(1, "Skill message did not match pattern: " .. text);

end


------------------------------------------------------------------------------
-- OnMoneyMessage
-- There's been a mmoney loot message in chat - process it.
------------------------------------------------------------------------------

function LT_OnMoneyMessage(text)

    LT_DebugMessage(4, "Raw Money Message: " .. text);

    -- "You loot 1 Silver, 10 Copper"   
    local beginMatch, endMatch, amount = string.find(text, LT_MONEY_LOOT);
    if (beginMatch) then
        -- This shouldn't happen, but validate that we got our out parameters.
        if (amount == nil) then
            LT_DebugMessage(2, "Amount was nil");
            return;
        end
        
        -- Non-shared money

        -- We ignore non-shared loot messages that come from chat if we're in
        -- "solo" mode.  We'll get a LT_OnLootSlotCleared event for those ones 
        -- consistently.  We only get the chat message when it's a shift-click 
        -- loot.
        if (not LT_IsPlayerSolo()) then
            LT_OnMoneyLootText(amount, false);
        else
            LT_DebugMessage(2, "Ignoring loot chat message.");
        end
        
        return;
    end
    
    --  "Your share of the loot is 2 Silver, 21 Copper."
    local beginMatch, endMatch, amount = string.find(text, LT_SHARED_MONEY_LOOT);
    if (beginMatch) then
        -- This shouldn't happen, but validate that we got our out parameters.
        if (amount == nil) then
            LT_DebugMessage(2, "Amount was nil");
            return;
        end
        
        -- Shared money
        LT_OnMoneyLootText(amount, true);
        
        return;
    end
    
    LT_DebugMessage(2, "Money message did not match pattern: " .. text);

end


------------------------------------------------------------------------------
-- OnMoneyLootText
-- Parse and process cash loot
------------------------------------------------------------------------------

function LT_OnMoneyLootText(text, shared)

    -- 1 Gold, 2 Silver, 21 Copper."
    local value, gold, silver, copper = LT_ParseMoney(text);
    
    local sharedString = "";
    if (shared) then
        sharedString = " (shared)"
    end
    LT_DebugMessage(2, "Looted Money" .. sharedString .. ": " .. tostring(gold) .. "g " .. tostring(silver) .. "s " .. tostring(copper) .. "c");
    
    return LT_OnMoneyLoot(value, shared, nil);
    
end


------------------------------------------------------------------------------
-- OnMoneyLoot
-- Process cash loot
------------------------------------------------------------------------------

function LT_OnMoneyLoot(value, shared, source)

    local rawItem = {};
    rawItem.Name = LT_MONEY;
    rawItem.Quality = -1;
    rawItem.Class = LT_MONEY;

    local item = LT_CreateItemFromRaw(rawItem, source);

    if (item ~= nil) then
        if (item.Value == nil) then
            item.Value = 0;
        end

        -- The Value of the Money item is an average drop size.
        -- Add this new loot value into the running average.

        local oldValue = item.Value;
        local newCount = getn(item.TimesLooted);

        local oldTotalMoneyLooted = oldValue * (newCount - 1);
        local newTotalMoneyLooted = oldTotalMoneyLooted + value;
        local newAverage = newTotalMoneyLooted / newCount;

        item.Value = newAverage;

        -- If this is shared loot, go to "Everyone", otherwise go to the local player.
        local recipient = LT_EVERYONE;
        if (not shared) then
            recipient = UnitName("player");
        end

        -- Add the receiving player to the item's Recipients list
        if (item.Recipients == nil) then
            item.Recipients = { };
        end
        LT_AddCountEntry(item.Recipients, recipient, 1);

        return item;

    end

end


------------------------------------------------------------------------------
-- OnReputationChange
-- There has been a reputation gain/loss.
------------------------------------------------------------------------------

function LT_OnReputationChange(faction, amount)

    LT_DebugMessage(1, string.format("Faction gain: %s - %d", faction, amount));

    -- Create a fake item entry to represent the reputation
    local rawItem = {};
    rawItem.Quality = -2;
    
    -- Give separate names for gain/loss
    local formatString = LT_REPUTATION_TYPE;
    if (amount < 0) then    
        formatString = LT_REPUTATION_TYPELOST;
    end
        
    rawItem.Name = string.format(formatString, faction);
    rawItem.Class = LT_REPUTATION;
    rawItem.SubClass = faction;
    

    -- Create the final item entry
    local item = LT_CreateItemFromRaw(rawItem, nil);

    if (item ~= nil) then

        local recipient = UnitName("player");

        -- Add the receiving player to the item's Recipients list
        if (item.Recipients == nil) then
            item.Recipients = { };
        end
        LT_AddCountEntry(item.Recipients, recipient, amount);

        return item;

    end

end


------------------------------------------------------------------------------
-- OnGainExperienceAmount
-- There's been an experience gain.
------------------------------------------------------------------------------

function LT_OnGainExperienceAmount(amount, source)

    LT_DebugMessage(1, string.format("Experience gain: %d (%s)", amount, source));
    
    -- Create a fake item entry to represent the experience
    local rawItem = {};
    rawItem.Quality = -2;
    rawItem.Name = LT_EXPERIENCE;
    rawItem.Class = LT_EXPERIENCE;

    -- Create the real item entry
    local item = LT_CreateItemFromRaw(rawItem, nil);

    if (item ~= nil) then

        local recipient = UnitName("player");

        -- Add the receiving player to the item's Recipients list
        if (item.Recipients == nil) then
            item.Recipients = { };
        end
        LT_AddCountEntry(item.Recipients, recipient, amount);

        if (source ~= nil) then
            LT_AddSource(item, source, amount);
        else
            LT_DebugMessage(1, "Unspecified source for experience");
        end

        return item;

    end
    
    

end


------------------------------------------------------------------------------
-- OnGainHonorAmount
-- There's been an honor gain.
------------------------------------------------------------------------------

function LT_OnGainHonorAmount(amount, source)

    LT_DebugMessage(1, string.format("Honor gain: %d", amount));
    
    -- Create a fake item entry to represent the honor
    local rawItem = {};
    rawItem.Quality = -2;
    rawItem.Name = LT_HONOR;
    rawItem.Class = LT_HONOR;

    -- Create the real item entry
    local item = LT_CreateItemFromRaw(rawItem, nil);

    if (item ~= nil) then

        local recipient = UnitName("player");

        -- Add the receiving player to the item's Recipients list
        if (item.Recipients == nil) then
            item.Recipients = { };
        end
        LT_AddCountEntry(item.Recipients, recipient, amount);
        
        if (source ~= nil) then
            LT_AddSource(item, source, amount);
        else
            LT_DebugMessage(1, "Unspecified source for honor");
        end

        return item;

    end

end


------------------------------------------------------------------------------
-- OnLootBegin
-- The current player has opened the loot window.
------------------------------------------------------------------------------

function LT_OnLootBegin()

    LT_PendingLootBySlot = {};

    -- Determine the target being looted
    local source = UnitName("target");
    
    if (source == nil) then
        LT_DebugMessage(3, "Looting (Unknown)");
    else
        LT_DebugMessage(3, "Looting " .. source);
    end
    
    local lootCount = GetNumLootItems();
    
    for slot = 1, lootCount, 1 do

        -- Create a pending loot entry
        LT_CreatePendingLoot(slot, source);
    end
    
end


------------------------------------------------------------------------------
-- OnLootEnd
-- The current player has closed the loot window.
------------------------------------------------------------------------------

function LT_OnLootEnd()

    LT_DebugMessage(3, "Done looting");
    
end


------------------------------------------------------------------------------
-- OnLootEnd
-- The current player has closed the loot window.
------------------------------------------------------------------------------

function LT_OnLootSlotCleared(slot)

    LT_DebugMessage(3, "Slot looted: " .. slot);
    
    if (LT_PendingLootBySlot == nil) then
        LT_DebugMessage(2, "Invalid slot looting.  Expected pending loot table to still be valid.");
        return;
    end
    
    local loot = LT_PendingLootBySlot[slot];
    
    if (loot == nil) then
        LT_DebugMessage(2, "Invalid slot looting.  Expected pending loot item to still be valid for slot " .. slot .. ".");
        return;
    end
    
    LT_DebugMessage(4, "Loot cleared: " .. loot.Name .. " (slot " .. slot .. ")");
    
    local settings = LT_GetSettings();
    if (loot.Name == LT_MONEY) then
        -- Disabling because we can't determine at this point if this will
        -- end up being a shared (split) money drop or not.  We will always
        -- get this event for the local player, and so when solo-ing this is
        -- the best event to hook (since the chat message is *not* always
        -- sent).  However, when in a group we don't want to think the local
        -- player will receive 100% of the cash when in fact it will be split.
        if (LT_IsPlayerSolo()) then
            -- Non-shared loot
            local item = LT_OnMoneyLoot(loot.Value, false, loot.Source);
        end
        
        LT_PendingLoot[loot.Name] = nil;
    end
    
    LT_PendingLootBySlot[slot] = nil;
    
end


------------------------------------------------------------------------------
-- OnTargetChanged
-- The current player has changed who they're targeting.
------------------------------------------------------------------------------

function LT_OnTargetChanged()

    LT_CreatePendingTarget();

end


------------------------------------------------------------------------------
-- SlashCommand
-- Handles console commands.
------------------------------------------------------------------------------

function LT_OnSlashCommand(rawArgument)

    local command, value = LT_GetCommandAndValue(rawArgument);
    
    local settings = LT_GetSettings();
    
    if (LT_StrIsNilOrEmpty(command)) then
        command = LT_SLASHCOMMAND_SETTINGS;
    end
    
    if (command == LT_SLASHCOMMAND_DEBUG) then
        
        if (value ~= nil) then
            local level = tonumber(value);
            if (level == nil) then
                LT_Message(LT_SLASHCOMMAND_DEBUG_ERROR);
            else
                settings.DebugLevel = level;
            end
        end
        
        LT_Message(string.format(LT_SLASHCOMMAND_DEBUG_QUERY, settings.DebugLevel));
        return;
        
    elseif (command == LT_SLASHCOMMAND_JUSTMYLOOT) then
    
        if (value ~= nil) then
            settings.JustMyLoot = value;
        else
            LT_Message(string.format(LT_SLASHCOMMAND_JUSTMYLOOT_QUERY, tostring(settings.JustMyLoot)));
        end;
        return;
        
    elseif (command == LT_SLASHCOMMAND_SESSION) then
    
        if (value ~= nil) then
            LT_SetSession(value);
        else
            LT_Message(string.format(LT_SLASHCOMMAND_SESSION_QUERY, settings.CurrentSession));
        end;
            
        return;
        
    elseif (command == LT_SLASHCOMMAND_SESSIONS) then
    
        LT_Message(LT_SLASHCOMMAND_SESSIONS_QUERY);
        LT_MessageIndent();
    
        local sessions = LT_GetAvailableSessions();
        foreach(sessions, function(k,v)
            LT_Message(LT_FormatSessionName(v, true));
        end);
        
        LT_MessageUnindent();
        
        return;
        
    elseif (command == LT_SLASHCOMMAND_EXPORT) then
    
        LT_ExportSession(value);
        return;
        
    elseif (command == LT_SLASHCOMMAND_SUMMARY) then
    
        LT_OutputSummary(value);
        return;
        
    elseif (command == LT_SLASHCOMMAND_CHATCOLOR) then
    
        if (value ~= nil) then
        
            -- TODO: Validate that this parses comma separated RGB values as well as space values,
            --       Because I don't think it does...
            local beginMatch, endMatch, r, g, b = string.find(value, "(%d+)[%s,](%d+)[%s,](%d+)");
            
            if (beginMatch) then
            
                if ((r == nil) or (g == nil) or (b == nil)) then
                    LT_Message(LT_SLASHCOMMAND_CHATCOLOR_ERROR);
                else
                    settings.ChatColor = {};
                    settings.ChatColor.r = r;
                    settings.ChatColor.g = g;
                    settings.ChatColor.b = b;
                end
            else
                LT_Message(LT_SLASHCOMMAND_CHATCOLOR_ERROR);
            end
        end;
        
        LT_Message(string.format(LT_SLASHCOMMAND_CHATCOLOR_QUERY, settings.ChatColor.r, settings.ChatColor.g, settings.ChatColor.b));
        return;
        
    elseif (command == LT_SLASHCOMMAND_THRESHOLD) then
        if (value ~= nil) then
            settings.QualityThreshold = tonumber(value);
            LT_FireChange();
        else
            LT_Message(LT_SLASHCOMMAND_THRESHOLD_ERROR);
        end
        
        LT_Message(string.format(LT_SLASHCOMMAND_THRESHOLD_QUERY, settings.QualityThreshold));
        return;
        
    elseif (command == LT_SLASHCOMMAND_RESET) then
    
        LT_ResetSession(value);
        return;
        
    elseif (command == LT_SLASHCOMMAND_UPDATE) then
    
        if (value ~= nil) then
            LT_UpdateObject(value);
        else
            LT_Message(LT_SLASHCOMMAND_UPDATE_ERROR);
        end
        return;
        
    elseif (command == LT_SLASHCOMMAND_TRANSFER) then
    
        if (value ~= nil) then
            -- /lt transfer playerNameSource playerNameTarget itemName
            local beginMatch, endMatch, playerNameSource, playerNameTarget, itemName = string.find(value, "([^%s]*)%s([^%s]*)%s(.*)");
            if (beginMatch) then
                LT_TransferItem(playerNameSource, playerNameTarget, itemName);
            else
                LT_Message(LT_SLASHCOMMAND_TRANSFER_ERROR);
            end
            
        end
    
        return;
        
    elseif (command == LT_SLASHCOMMAND_SETTINGS) then
    
        if (LT_SettingsUI:IsShown()) then
            HideUIPanel(LT_SettingsUI);
        else
            ShowUIPanel(LT_SettingsUI);
        end
    
        return;
        
    elseif (command == LT_SLASHCOMMAND_HELP) then
    
        -- Fall through to the help code below.

    elseif (rawArgument ~= nil) then
    
        LT_Message(string.format(LT_SLASHCOMMAND_ERROR, rawArgument));
        
    end
    
    -- Show the help
    LT_Message(string.format(LT_HELPMESSAGE_1, LT_Version));
    LT_Message(LT_HELPMESSAGE_2);
    LT_Message(LT_HELPMESSAGE_3);
    LT_Message(LT_HELPMESSAGE_4);
    LT_Message(LT_HELPMESSAGE_5);
    LT_Message(LT_HELPMESSAGE_6);
    LT_Message(LT_HELPMESSAGE_7);
    LT_Message(LT_HELPMESSAGE_8);
    LT_Message(LT_HELPMESSAGE_9);
    LT_Message(LT_HELPMESSAGE_10);
    LT_Message(LT_HELPMESSAGE_11);
    LT_Message(LT_HELPMESSAGE_12);

end


------------------------------------------------------------------------------
-- AddListener
------------------------------------------------------------------------------

function LT_AddListener(event)

    if (LT_ChangeListeners == nil) then
        LT_ChangeListeners = {};
    end

    tinsert(LT_ChangeListeners, event);

end


------------------------------------------------------------------------------
-- RemoveListener
------------------------------------------------------------------------------

function LT_RemoveListener(event)

    if (LT_ChangeListeners == nil) then
        return;
    end

    tremove(LT_ChangeListeners, LT_IndexOf(LT_ChangeListeners, event));

end


------------------------------------------------------------------------------
-- FireChange
------------------------------------------------------------------------------

function LT_FireChange()

    if (LT_ChangeListeners ~= nil) then
        foreach(LT_ChangeListeners, function(k,v)
            v();
        end);
    end

end

