local GetTime = QuestHelperCollector_GetTime

QuestHelper_File["collect_loot.lua"] = "4.0.1.$svnversion$"
QuestHelper_Loadtime["collect_loot.lua"] = GetTime()

local debug_output = false
if QuestHelper_File["collect_loot.lua"] == "Development Version" then debug_output = true end

local QHC

local PseudoIDs = {
  ["gold"] = -1,
}

local GetMonsterUID
local GetMonsterType

local GetItemType

local GetLoc

local Patterns

local members = {}
local members_count = 0
local members_refs = {} -- "raid6" and the like

local function MembersUpdate()
  -- We want to keep track of exactly who is in a group with this player, so we can watch for combat messages involving them, so we can see who's been tapped, so we can record the right deaths, so we can know who the player should be able to loot.
  -- I hate my life.
  -- >:(
  
  local alone = false
  
  --QuestHelper:TextOut("MU start")
  members = {} -- we burn a table every time this updates, but whatever
  members_count = 0
  if GetNumRaidMembers() > 0 then
    -- we is in a raid
    for i = 1, 40 do
      local ite = string.format("raid%d", i)
      local gud = UnitGUID(ite)
      if gud then
        members[gud] = true
        table.insert(members_refs, ite)
        --QuestHelper:TextOut(string.format("raid member %s added", UnitName(ite)))
      end
    end
  elseif GetNumPartyMembers() > 0 then
    -- we is in a party
    for i = 1, 4 do
      local ite = string.format("party%d", i)
      local gud = UnitGUID(ite)
      if gud then
        members[gud] = true
        table.insert(members_refs, ite)
        --QuestHelper:TextOut(string.format("party member %s added", UnitName(ite)))
      end
    end
    members[UnitGUID("player")] = true
    table.insert(members_refs, "player")
    --QuestHelper:TextOut(string.format("player %s added", UnitName("player"))) 
  else
    -- we is alone ;.;
    if UnitGUID("player") then members[UnitGUID("player")] = true end -- it's possible that we haven't logged in entirely yet
    table.insert(members_refs, "player")
    --QuestHelper:TextOut(string.format("player %s added as %s", UnitName("player"), tostring(UnitGUID("player"))))
    alone = true
  end
  
  if GetLootMethod() == "master" and not alone then members = {} members_refs = {} end -- We're not going to bother trying to deal with master loot right now - it's just too different and I just don't care enough.
  
  for _, _ in pairs(members) do members_count = members_count + 1 end -- lulz
end

local MS_TAPPED_US = 1
local MS_TAPPED_US_TRIVIAL = 2
local MS_TAPPED_OTHER = 3
local MS_TAPPED_LOOTABLE = 4
local MS_TAPPED_LOOTABLE_TRIVIAL = 5
local MS_TAPPED_LOOTED = 6

local last_cleanup = GetTime()

local monsterstate = {}
local monsterrefresh = {}
local monstertimeout = {}

-- This all does something quite horrible.
-- Some monsters don't become lootable when they're killed and didn't drop anything. We need to record this so we can get real numbers for them. 
-- Unfortunately, we can't just record when "something" is killed. We have to record when "our group" killed it, so we know that there *was* a chance of looting it.
-- As such, we need to check for monster deaths that the player may never have actually targeted. It gets, to put it mildly, grim, and unfortunately we'll never be able to solve it entirely.
-- Worse, we need to *not* record item drops for things that we never actually "saw" but that were lootable anyway, because if we do, we bias the results towards positive (i.e. if we AOE ten monsters down, and two of them drop, and we loot those, that's 2/2 if we record the drops, and 0/0 if we don't, while what we really want is 2/10. 0/0 is at least "not wrong".)
-- On top of this, we want to avoid looting "discarded items", but unfortunately there's no real good way to determine this. Welp.
local last_scan = 0
-- Here's a little bottleneck - we're going to try to only do one scan per second. Fingers crossed.

local function CombatLogEvent(_, event, sourceguid, _, _, destguid, _, _, _, spellname)
  -- There's many things that are handled here.
  -- First, if there's any damage messages coming either to or from a party member, we check to see if that monster is tapped by us. If it's tapped, we cache the value for 15 seconds, expiring entirely in 30.
  -- Second, there's the Death message. If it's tapped by us, increases the kill count by 1/partymembers and changes its state to lootable.
  if event ~= "UNIT_DIED" then
    if last_scan > GetTime() then return end -- welp
    
    -- Something has been attacked by something, maybe.
    if not string.find(event, "_DAMAGE$") then return end -- We only care about something punching something else.
    
    local target, source
    if members[sourceguid] then
      source = sourceguid
      target = destguid
    elseif members[destguid] then
      source = targetguid
      target = sourceguid
    end -- If one of the items is in our party, the other is our target.
    
    if not target then return end   -- If we don't have a target, then nobody is in our party, and we don't care.
    
    if monsterrefresh[target] and monsterrefresh[target] > GetTime() then return end -- we already have fresh data, so we're good
    
    -- Now comes the tricky part. We can't just look at the target because we're not allowed to target by GUID. So we iterate through all the party/raid members and hope *someone* has it targeted. Luckily, we can stop once we find someone who does.
    local targ
    for _, v in pairs(members_refs) do
      targ = v .. "target"   if UnitGUID(targ) == target then break end
      targ = nil
    end
    last_scan = GetTime() + 1
    
    if not targ then
      --monsterrefresh[target] = GetTime() + 5
      --monstertimeout[target] = GetTime() + 5
      return
    end -- Well, nobody seems to be targeting it. That's . . . odd, and annoying. We were thinking of tossing on a 5-second timeout. But we didn't, because we decided the global one-second lockout might help. So we'll do that for now.
    
    -- Okay. So we know who's targeting it. Now, let's see who has it tapped, if anyone.
    if not UnitIsTapped(targ) then
      -- Great. Nobody is. That is just *great*. Look how exuberant I feel at this moment. You know what? 5-second timeout.
      monsterstate[target] = nil
      monsterrefresh[target] = GetTime() + 5
      monstertimeout[target] = GetTime() + 5
      --if debug_output then QuestHelper:TextOut(string.format("Monster ignorified")) end
    else
      -- We know someone is, so we're going to set up our caching . . .
      monsterrefresh[target] = GetTime() + 15
      monstertimeout[target] = GetTime() + 30
      monsterstate[target] = (UnitIsTappedByPlayer(targ)) and (not UnitIsTrivial(targ) and MS_TAPPED_US or MS_TAPPED_US_TRIVIAL) or MS_TAPPED_OTHER -- and figure out if it's us. Or if it's trivial. We somewhat-ignore it if it's trivial, since it's much less likely to be looted and that could throw off our numbers
      --if debug_output then QuestHelper:TextOut(string.format("Monster %s set to %d, %s, %s", target, monsterstate[target], tostring(UnitIsTappedByPlayer(targ)), tostring(UnitIsTrivial(targ)))) end
    end
    
    -- DONE
  else
    -- It's dead. Hooray!
    
    if monsterstate[destguid] and monstertimeout[destguid] > GetTime() and (monsterstate[destguid] == MS_TAPPED_US or monsterstate[destguid] == MS_TAPPED_US_TRIVIAL) and members_count > 0 then -- yaaay
      local type = GetMonsterType(destguid)
      if not QHC.monster[type] then QHC.monster[type] = {} end
      if monsterstate[destguid] == MS_TAPPED_US then
        QHC.monster[type].kills = (QHC.monster[type].kills or 0) + 1 / members_count  -- Hopefully, most people loot their kills. Divide by members_count 'cause there's a 1/members chance that we get to loot. We'll flag the loot count some other way if it was a trivial monster.
      end
      
      monsterstate[destguid] = (monsterstate[destguid] == MS_TAPPED_US and MS_TAPPED_LOOTABLE or MS_TAPPED_LOOTABLE_TRIVIAL)
      monsterrefresh[destguid] = GetTime() + 600
      monstertimeout[destguid] = GetTime() + 600
      --QuestHelper:TextOut(string.format("Tapped monster %s slain, set to lootable", destguid))
    else
      monsterstate[destguid] = nil
      monsterrefresh[destguid] = nil
      monstertimeout[destguid] = nil
      --QuestHelper:TextOut(string.format("Untapped monster %s slain, cleared", destguid))
    end
  end
end

local skintypes = {}

skintypes[UNIT_SKINNABLE_ROCK] = "mine"
skintypes[UNIT_SKINNABLE_HERB] = "herb"
skintypes[UNIT_SKINNABLE_BOLTS] = "eng"
skintypes[UNIT_SKINNABLE_LEATHER] = "skin"

local function SkinnableflagsTooltipy(self, ...)
  --QuestHelper:TextOut("tooltipy")
  if UnitExists("mouseover") and UnitIsVisible("mouseover") and not UnitIsPlayer("mouseover") and not UnitPlayerControlled("mouseover") and UnitIsDead("mouseover") then
    local guid = UnitGUID("mouseover")
    --QuestHelper:TextOut("critar")
    if not monsterstate[guid] or monsterstate[guid] ~= MS_TAPPED_LOOTED or monsterrefresh[guid] > GetTime() then return end
    --QuestHelper:TextOut("runin")
    
    local cid = GetMonsterType(guid)
    
    local skintype = nil
    
    local lines = GameTooltip:NumLines()
    for i = 3, lines do
      --QuestHelper:TextOut(_G["GameTooltipTextLeft" .. tostring(i)]:GetText())
      local skeen = skintypes[_G["GameTooltipTextLeft" .. tostring(i)]:GetText()]
      if skeen then QuestHelper: Assert(not skintype) skintype = skeen end
    end
    
    if not QHC.monster[cid] then QHC.monster[cid] = {} end
    local qhci = QHC.monster[cid]
    
    for _, v in pairs(skintypes) do
      if v == skintype then
        --QuestHelper:TextOut(v .. "_yes")
        qhci[v .. "_yes"] = (qhci[v .. "_yes"] or 0) + 1
      else
        --QuestHelper:TextOut(v .. "_no")
        qhci[v .. "_no"] = (qhci[v .. "_no"] or 0) + 1
      end
    end
  end
end

-- Logic behind this module:
-- Watch for the spell to be sent
-- Watch for it to start
-- Check out the combat log and see what GUID we get
-- If the GUID is null, we're targeting an object, otherwise, we're targeting a critter
-- Wait for spell to succeed
-- If anything doesn't synch up, or the spell is interrupted, nil out all these items.
-- We've got a little special case for pickpocketing, because people often use macros, so we detect that case specifically.

local PP_PHASE_IDLE
local PP_PHASE_SENT
local PP_PHASE_COMPLETE

local pickpocket_phase = PP_PHASE_IDLE
local pickpocket_target
local pickpocket_otarget_guid
local pickpocket_timestamp

local pickpocket_name = GetSpellInfo(921)   -- this is the pickpocket spell ID

local function pp_reset()
  pickpocket_target, pickpocket_otarget_guid, pickpocket_timestamp, pickpocket_phase = nil, nil, nil, PP_PHASE_IDLE
end
pp_reset()

local function PPSent(player, spell, _, target)
  if player ~= "player" then return end
  if spell ~= pickpocket_name then return end
  if UnitName("target") ~= target then return end -- DENY
  
  pickpocket_timestamp, pickpocket_target, pickpocket_otarget_guid, pickpocket_phase = GetTime(), target, UnitGUID("target"), PP_PHASE_SENT
end

local function PPSucceed(player, spell, rank)
  if player ~= "player" then return end
  if spell ~= pickpocket_name then return end
  
  if pickpocket_phase ~= PP_PHASE_SENT and (not pickpocket_otarget_guid or last_timestamp + 1 < GetTime()) then
    pp_reset()
    return
  end
  
  pickpocket_timestamp, pickpocket_phase = GetTime(), PP_PHASE_COMPLETE
end


-- This segment deals with openable containers

local touched_itemid
local touched_timestamp

local function ItemLock(bag, slot)
  if not bag or not slot then return end -- probably changing equipment
  
  local _, _, locked = GetContainerItemInfo(bag, slot)
  --QuestHelper:TextOut(string.format("trying lock %s", tostring(locked)))
  if locked then
    touched_itemid = GetItemType(GetContainerItemLink(bag, slot))
    --[[QuestHelper:TextOut(string.format("trying lock %s", tostring(touched_itemid)))
    QuestHelper:TextOut(string.format("trying lock %s", tostring(type(touched_itemid))))
    QuestHelper:TextOut(string.format("trying lock %s", tostring(QHC.item[touched_itemid].open_yes)))
    QuestHelper:TextOut(string.format("trying lock %s", tostring(QHC.item[touched_itemid].open_no)))]]
    touched_timestamp = GetTime()
    if not QHC.item[touched_itemid] or (QHC.item[touched_itemid].open_yes or 0) <= (QHC.item[touched_itemid].open_no or 0) then
      touched_itemid = nil
      touched_timestamp = nil
    end
  else
    touched_itemid = nil
    touched_timestamp = nil
  end
end


-- Here's the segment for longer spells. There aren't any instant spells we currently care about, besides pickpocketing. This will probably change eventually (arrows in the DK starting zone?)

local LAST_PHASE_IDLE = 0
local LAST_PHASE_SENT = 1
local LAST_PHASE_START = 2
local LAST_PHASE_COMBATLOG = 3
local LAST_PHASE_COMPLETE = 4

local last_phase = LAST_PHASE_IDLE
local last_spell
local last_rank
local last_target
local last_target_guid
local last_otarget
local last_otarget_guid
local last_timestamp
local last_succeed = false
local last_succeed_trade = GetTime()

local gathereffects = {}

gathereffects[GetSpellInfo(51306)] = {token = "eng", noclog = true}
gathereffects[GetSpellInfo(32606)] = {token = "mine"}
gathereffects[GetSpellInfo(2366)] = {token = "herb"}
gathereffects[GetSpellInfo(8613)] = {token = "skin"}
gathereffects[GetSpellInfo(21248)] = {token = "open", noclog = true}
gathereffects[GetSpellInfo(30427)] = {token = "extract", noclog = true} -- not a loot window, so it won't really work, but hey
gathereffects[GetSpellInfo(13262)] = {token = "de", noclog = true, ignore = true}
gathereffects[GetSpellInfo(31252)] = {token = "prospect", noclog = true, ignore = true}
gathereffects[GetSpellInfo(51005)] = {token = "mill", noclog = true, ignore = true}


local function normalize_spell(spell)
  for k in pairs(gathereffects) do
    if spell:find(k) then return k end
  end
  return spell
end


local function last_reset()
  last_timestamp, last_spell, last_rank, last_target, last_target_guid, last_otarget, last_otarget_guid, last_succeed, last_phase = nil, nil, nil, nil, nil, nil, false, LAST_PHASE_IDLE
end
last_reset()

-- This all doesn't work with instant spells. Luckily, I don't care about instant spells (yet).
local function SpellSent(player, spell, rank, target)
  if player ~= "player" then return end
  spell = normalize_spell(spell)
  
  last_timestamp, last_spell, last_rank, last_target, last_target_guid, last_otarget, last_otarget_guid, last_succeed, last_phase = GetTime(), spell, rank, target, nil, UnitName("target"), UnitGUID("target"), false, LAST_PHASE_SENT
  
  if last_otarget and last_otarget ~= last_target then last_reset() return end
  
  --QuestHelper:TextOut(string.format("ss %s", spell))
end

local function SpellStart(player, spell, rank)
  if player ~= "player" then return end
  spell = normalize_spell(spell)
  
  if spell ~= last_spell or rank ~= last_rank or last_target_guid or last_phase ~= LAST_PHASE_SENT or last_timestamp + 1 < GetTime() then
    last_reset()
  else
    --QuestHelper:TextOut(string.format("sst %s", spell))
    last_timestamp, last_phase = GetTime(), LAST_PHASE_START
  end
end

local function SpellCombatLog(_, event, sourceguid, _, _, destguid, _, _, _, spellname)
  if event ~= "SPELL_CAST_START" then return end
  
  if sourceguid ~= UnitGUID("player") then return end
  spellname = normalize_spell(spellname)
  
  --QuestHelper:TextOut(string.format("cle_ss enter %s %s %s %s", tostring(spellname ~= last_spell), tostring(not last_target), tostring(not not last_target_guid), tostring(last_timestamp + 1 < GetTime())))
  
  if spellname ~= last_spell or not last_target or last_target_guid or last_timestamp + 1 < GetTime() then
    last_reset()
    return
  end
  
  --QuestHelper:TextOut("cle_ss enter")
  
  if last_phase ~= LAST_PHASE_START  then
    last_reset()
    return
  end
  
  --QuestHelper:TextOut(string.format("cesst %s", spellname))
  last_timestamp, last_target_guid, last_phase = GetTime(), destguid, LAST_PHASE_COMBATLOG
  
  if last_target_guid == "0x0000000000000000" then last_target_guid = nil end
  if last_target_guid and last_target_guid ~= last_otarget_guid then last_reset() return end
end

local function SpellSucceed(player, spell, rank)
  if player ~= "player" then return end
  spell = normalize_spell(spell)
  
  if gathereffects[spell] then last_succeed_trade = GetTime() end
  
  --QuestHelper:TextOut(string.format("sscu enter %s %s %s %s %s", tostring(last_spell), tostring(last_target), tostring(last_rank), tostring(spell), tostring(rank)))
  
  if not last_spell or not last_target or last_spell ~= spell or last_rank ~= rank then
    last_reset()
    return
  end
  
  --QuestHelper:TextOut("sscu enter")
  
  if gathereffects[spell] and gathereffects[spell].noclog then
    if last_phase ~= LAST_PHASE_START or last_timestamp + 10 < GetTime() then
      last_reset()
      return
    end
  else
    if last_phase ~= LAST_PHASE_COMBATLOG or last_timestamp + 10 < GetTime() then
      last_reset()
      return
    end
  end
  
  --QuestHelper:TextOut(string.format("sscu %s, %d, %s, %s", spell, last_phase, tostring(last_phase == LAST_PHASE_SENT), tostring((last_phase == LAST_PHASE_SENT) and LAST_PHASE_SHORT_SUCCEEDED)))
  last_timestamp, last_succeed, last_phase = GetTime(), true, LAST_PHASE_COMPLETE
  --QuestHelper:TextOut(string.format("last_phase %d", last_phase))
  
  --[[if last_phase == LAST_PHASE_COMPLETE then
    QuestHelper:TextOut(string.format("spell succeeded, casting %s %s on %s/%s", last_spell, last_rank, tostring(last_target), tostring(last_target_guid)))
  end]]
end

local function SpellInterrupt(player, spell, rank)
  if player ~= "player" then return end
  
  -- I don't care what they were casting, they're certainly not doing it now
  --QuestHelper:TextOut(string.format("si %s", spell))
  last_reset()
end

local function LootOpened()

  local targetguid = UnitGUID("target")

  -- We're cleaning up the monster charts here, on the theory that if someone is looting, they're okay with a tiny lag spike.
  if last_cleanup + 300 < GetTime() then
    local cleanup = {}
    for k, v in pairs(monstertimeout) do
      if v < GetTime() then table.insert(cleanup, k) end
    end
    
    for _, v in pairs(cleanup) do
      monsterstate[v] = nil
      monsterrefresh[v] = nil
      monstertimeout[v] = nil
    end
  end

  -- First off, we try to figure out where the hell these items came from.
  
  local ltype = nil
  local spot = nil
  local prefix = nil
  
  if IsFishingLoot() then
    -- yaaaaay
    --if debug_output then QuestHelper:TextOut("Fishing loot") end
    local loc = GetLoc()
    if not QHC.fishing then QHC.fishing = {} end
    ltype = QHC.fishing
    spot = {}
    spot.loc = loc
    prefix = "fish"
    
  elseif pickpocket_phase == PP_PHASE_COMPLETE and pickpocket_timestamp and pickpocket_timestamp + 1 > GetTime() and targetguid == pickpocket_otarget_guid then
    --if debug_output then QuestHelper:TextOut(string.format("Pickpocketing from %s/%s", pickpocket_target, UnitName("target"), targetguid)) end
    local mid = GetMonsterType(targetguid)
    if not QHC.monster[mid] then QHC.monster[mid] = {} end
    spot = QHC.monster[mid]
    prefix = "rob"
    
  elseif last_phase == LAST_PHASE_COMPLETE and gathereffects[last_spell] and last_timestamp + 1 > GetTime() then
    local beef = string.format("%s/%s %s/%s", tostring(last_target), tostring(last_target_guid), tostring(last_otarget), tostring(last_otarget_guid))
    
    if gathereffects[last_spell].ignore then return end
    
    prefix = gathereffects[last_spell].token
    
    -- this one is sort of grim actually
    -- If we have an last_otarget_guid, it's the right one, and it's a monster
    -- If we don't, use last_target, and it's an object
    -- This is probably going to be buggy. Welp.
    if last_otarget_guid then
      if debug_output then QuestHelper:TextOut(string.format("%s from monster %s", gathereffects[last_spell].token, beef)) end
      local mid = GetMonsterType(last_otarget_guid)
      if not QHC.monster[mid] then QHC.monster[mid] = {} end
      spot = QHC.monster[mid]
    else
      if debug_output then QuestHelper:TextOut(string.format("%s from object %s", gathereffects[last_spell].token, beef)) end
      if not QHC.object[last_target] then QHC.object[last_target] = {} end
      spot = QHC.object[last_target]
    end
    
  elseif touched_timestamp and touched_timestamp + 1 > GetTime() then
    -- Opening a container, possibly
    --if debug_output then QuestHelper:TextOut(string.format("Opening container %d", touched_itemid)) end
    if not QHC.item[touched_itemid] then QHC.item[touched_itemid] = {} end
    spot = QHC.item[touched_itemid]
    prefix = "open"
    
  elseif targetguid and (monsterstate[targetguid] == MS_TAPPED_LOOTABLE or monsterstate[targetguid] == MS_TAPPED_LOOTABLE_TRIVIAL) and monstertimeout[targetguid] > GetTime() and (not pickpocket_timestamp or pickpocket_timestamp + 5 < GetTime()) and (not last_timestamp or last_timestamp + 5 < GetTime()) and (last_succeed_trade + 5 < GetTime()) then -- haha holy shit
    -- Monster is lootable, so we loot the monster. Should we check to see if it's dead first? Probably.
    --if debug_output then QuestHelper:TextOut(string.format("%s from %s/%s", (monsterstate[targetguid] == MS_TAPPED_LOOTABLE and "Monsterloot" or "Trivial monsterloot"), UnitName("target"), targetguid)) end

    local mid = GetMonsterType(targetguid)
    if not QHC.monster[mid] then QHC.monster[mid] = {} end
    spot = QHC.monster[mid]
    if monsterstate[targetguid] == MS_TAPPED_LOOTABLE then
      prefix = "loot"
    elseif monsterstate[targetguid] == MS_TAPPED_LOOTABLE_TRIVIAL then
      prefix = "loot_trivial" -- might be a better way to do this, but we'll see
    end
    
    monsterstate[targetguid] = MS_TAPPED_LOOTED
    monstertimeout[targetguid] = GetTime() + 300
    monsterrefresh[targetguid] = GetTime() + 2
  else
    --if debug_output then QuestHelper:TextOut("Who knows") end  -- ugh
    local loc = GetLoc()
    if not QHC.worldloot then QHC.worldloot = {} end
    ltype = QHC.worldloot
    spot = {}
    spot.loc = loc
    prefix = "loot"
    
  end
  

  
  local items = {}
  items[PseudoIDs["gold"]] = 0
  for i = 1, GetNumLootItems() do
    tex, name, quant, qual, locked = GetLootSlotInfo(i)
    link = GetLootSlotLink(i)
    local curr = LootSlotIsCurrency(i)
    local coin = LootSlotIsCoin(i)
    local itm = LootSlotIsItem(i)

    if quant == 0 then
      -- moneys
      local tot = 0
      local _, _, gold = string.find(name, Patterns.GOLD_AMOUNT)
      local _, _, silver = string.find(name, Patterns.SILVER_AMOUNT)
      local _, _, copper = string.find(name, Patterns.COPPER_AMOUNT)
      tot = (tonumber(gold) or 0) * 10000 + (tonumber(silver) or 0) * 100 + (tonumber(copper) or 0) * 1
      items[PseudoIDs["gold"]] = tot
    else
      if curr then
        if not QHC.PseudoIDs[name] then
          QHC.PseudoIDsMin = QHC.PseudoIDsMin - 1
          QHC.PseudoIDs[name] = QHC.PseudoIDsMin
        end
        items[QHC.PseudoIDs[name]] = (items[QHC.PseudoIDs[name]] or 0) + quant
      else
        if not link and not name then
          local msg = "Texture is " .. tostring(tex) .. " with a quantity of " .. tostring(quant) .. ", a quality of " .. qual .. "."
          if coin then
            QuestHelper:Assert(false, "Loot slot " .. tostring(i) .. " is coin. " .. msg)
          elseif itm then
            QuestHelper:Assert(false, "Loot slot " .. tostring(i) .. " is item. " .. msg)
          else
	    QuestHelper_ErrorCatcher_ExplicitError(false, "Loot slot " .. tostring(i) .. " is ???. " .. msg)
            --QuestHelper:Assert(false, "Loot slot " .. tostring(i) .. " is ???. " .. msg)
          end
        elseif not link then
          QuestHelper:Assert(link, "Need more info on '" .. name .. "'x" .. tostring(quant) .. ".")
        elseif not name then
          QuestHelper:Assert(name, "Need more info on '" .. link .. "'x" .. tostring(quant) .. ".")
        end
        local itype = GetItemType(link)
        items[itype] = (items[itype] or 0) + quant
      end
    end
  end
  
  if not spot[prefix] then spot[prefix] = {} end
  spot[prefix]["count"] = (spot[prefix]["count"] or 0) + 1
  if not spot[prefix]["loot"] then spot[prefix]["loot"] = {} end
  local pt = spot[prefix]["loot"]
  for k, v in pairs(items) do
    if v > 0 then pt[k] = (pt[k] or 0) + v end
  end

  if ltype then table.insert(ltype, spot) end
end

function QH_Collect_Loot_Init(QHCData, API)
  QHC = QHCData
  
  if not QHC.monster then QHC.monster = {} end
  if not QHC.worldloot then QHC.worldloot = {} end
  if not QHC.fishing then QHC.fishing = {} end
  if not QHC.item then QHC.item = {} end
  if not QHC.PseudoIDs then
    QHC.PseudoIDs = PseudoIDs
    QHC.PseudoIDsMin = -1
  end
  
  QH_Event("PLAYER_ENTERING_WORLD", MembersUpdate)
  QH_Event("RAID_ROSTER_UPDATE", MembersUpdate)
  QH_Event("PARTY_MEMBERS_CHANGED", MembersUpdate)
  QH_Event("COMBAT_LOG_EVENT_UNFILTERED", CombatLogEvent)

  QH_Event("UPDATE_MOUSEOVER_UNIT", SkinnableflagsTooltipy)
  
  QH_Event("UNIT_SPELLCAST_SENT", PPSent)
  QH_Event("UNIT_SPELLCAST_SUCCEEDED", PPSucceed)
  
  QH_Event("ITEM_LOCK_CHANGED", ItemLock)
  
  QH_Event("UNIT_SPELLCAST_SENT", SpellSent)
  QH_Event("UNIT_SPELLCAST_START", SpellStart)
  QH_Event("COMBAT_LOG_EVENT_UNFILTERED", SpellCombatLog)
  QH_Event("UNIT_SPELLCAST_SUCCEEDED", SpellSucceed)
  QH_Event("UNIT_SPELLCAST_INTERRUPTED", SpellInterrupt)
  
  QH_Event("LOOT_OPENED", LootOpened)
  
  MembersUpdate() -- to get self, probably won't work but hey
  
  GetMonsterUID = API.Utility_GetMonsterUID
  GetMonsterType = API.Utility_GetMonsterType
  GetItemType = API.Utility_GetItemType
  QuestHelper: Assert(GetMonsterUID)
  QuestHelper: Assert(GetMonsterType)
  QuestHelper: Assert(GetItemType)
  
  Patterns = API.Patterns
  API.Patterns_RegisterNumber("GOLD_AMOUNT")
  API.Patterns_RegisterNumber("SILVER_AMOUNT")
  API.Patterns_RegisterNumber("COPPER_AMOUNT")
  
  GetLoc = API.Callback_LocationBolusCurrent
  QuestHelper: Assert(GetLoc)
  
  -- What I want to know is whether it was tagged by me or my group when dead
  -- Check target-of-each-groupmember? Once we see him tapped once, and by us, it's probably sufficient.
  -- Notes:
  --[[
  
  COMBAT_LOG_EVENT_UNFILTERED arg2 UNIT_DIED, PLAYER_TARGET_CHANGED, LOOT_OPENED, (LOOT_CLOSED, [LOOT_SLOT_CLEARED, ITEM_PUSH, CHAT_MSG_LOOT]), PLAYER_TARGET_CHANGED, SPELLCAST_SENT, SPELLCAST_START, SUCCEEDED/INTERRUPTED, STOP, LOOT_OPENED (etc)
  
  ITEM_PUSH can happen after LOOT_CLOSED, but it still happens.
  Between LOOT_OPENED and LOOT_CLOSED, the lootable target is still targeted. Unsure what happens when looting items. LOOT_CLOSED triggers first if we target someone else.
  ITEM_PUSH happens, then CHAT_MSG_LOOT. CHAT_MSG_LOOT includes quite a lot of potentially useful arguments.
  PLAYER_TARGET_CHANGED before either looting or skinning.
  SPELLCAST_SENT, SPELLCAST_START, SUCCEEDED/INTERRUPTED, STOP in that order. Arg4 on SENT seems to be the target's name. Arg4 on the others appears to be a unique identifier.
  When started, we target the right thing. After that, we don't seem to. Check the combat log.
  
  ]]
end
