
-- Award/highlight generator functions take the events and the scores as
-- produced by SCORING/CLSCORING and return a table if successful, or nil if
-- not and another one should be tried.

-- some globals we'll use a lot
local table = table
local pairs = pairs

local is_dmg = function(dmg_t, bit)
                  -- deal with large-number workaround for TableToJSON by
                  -- parsing back to number here
                  return util.BitSet(tonumber(dmg_t), bit)
               end

-- so much text here I'm using shorter names than usual
local T = LANG.GetTranslation
local PT = LANG.GetParamTranslation

-- a common pattern
local function FindHighest(tbl)
   local m_num = 0
   local m_id = nil
   for id, num in pairs(tbl) do
      if num > m_num then
         m_id = id
         m_num = num
      end
   end

   return m_id, m_num
end

local function FirstSuicide(events, scores, players, traitors)
   local fs = nil
   local fnum = 0
   for k, e in pairs(events) do
      if e.id == EVENT_KILL and e.att.sid64 == e.vic.sid64 then
         fnum = fnum + 1
         if fs == nil then
            fs = e
         end
      end
   end

   if fs then
      local award = {nick=fs.att.ni}
      if not award.nick then return nil end

      if fnum > 1 then
         award.title = T("aw_sui1_title")
         award.text =  T("aw_sui1_text")
      else
         award.title = T("aw_sui2_title")
         award.text = T("aw_sui2_text")
      end

      -- only high interest if many people died this way
      award.priority = fnum

      return award
   else
      return nil
   end
end

local function ExplosiveGrant(events, scores, players, traitors)
   local bombers = {}
   for k, e in pairs(events) do
      if e.id == EVENT_KILL and is_dmg(e.dmg.t, DMG_BLAST) then
         bombers[e.att.sid64] = (bombers[e.att.sid64] or 0) + 1
      end
   end

   local award = {title= T("aw_exp1_title")}

   if not table.IsEmpty(bombers) then
      for sid64, num in pairs(bombers) do
         -- award goes to whoever reaches this first I guess
         if num > 2 then
            award.nick = players[sid64]
            if not award.nick then return nil end -- if player disconnected or something

            award.text = PT("aw_exp1_text", {num = num})

            -- rare award, high interest
            award.priority = 10 + num

            return award
         end
      end
   end

   return nil
end

local function ExplodedSelf(events, scores, players, traitors)
   for k, e in pairs(events) do
      if e.id == EVENT_KILL and is_dmg(e.dmg.t, DMG_BLAST) and e.att.sid64 == e.vic.sid64 then
         return {title=T("aw_exp2_title"), text=T("aw_exp2_text"), nick=e.vic.ni, priority=math.random(1, 4)}
      end
   end

   return nil
end

local function FirstBlood(events, scores, players, traitors)
   for k, e in pairs(events) do
      if e.id == EVENT_KILL and e.att.sid64 != e.vic.sid64 and e.att.sid64 != -1 then
         local award = {nick=e.att.ni}
         if not award.nick or award.nick == "" then return nil end

         if e.att.tr and not e.vic.tr then -- traitor legit k
            award.title = T("aw_fst1_title")
            award.text = T("aw_fst1_text")
         elseif e.att.tr and e.vic.tr then -- traitor tk
            award.title = T("aw_fst2_title")
            award.text = T("aw_fst2_text")
         elseif not e.att.tr and not e.vic.tr then -- inno tk
            award.title = T("aw_fst3_title")
            award.text = T("aw_fst3_text")
         else -- inno legit k
            award.title = T("aw_fst4_title")
            award.text = T("aw_fst4_text")
         end

         -- more interesting if there were many players and therefore many kills
         award.priority = math.random(-3, math.Round(table.Count(players) / 4))

         return award
      end
   end
end

local function AllKills(events, scores, players, traitors)
   -- see if there is one killer responsible for all kills of either team

   local tr_killers = {}
   local in_killers = {}
   for id, s in pairs(scores) do
      if s.innos > 0 then
         table.insert(in_killers, id)
      elseif s.traitors > 0 then
         table.insert(tr_killers, id)
      end
   end

   if #tr_killers == 1 then
      local id = tr_killers[1]
      if not table.HasValue(traitors, id) then
         local killer = players[id]
         if not killer then return nil end

         return {nick=killer, title=T("aw_all1_title"), text=T("aw_all1_text"), priority=math.random(0, table.Count(players))}
      end
   end

   if #in_killers == 1 then
      local id = in_killers[1]
      if table.HasValue(traitors, id) then
         local killer = players[id]
         if not killer then return nil end

         return {nick=killer, title=T("aw_all2_title"), text=T("aw_all2_text"), priority=math.random(0, table.Count(players))}
      end
   end

   return nil
end

local function NumKills_Traitor(events, scores, players, traitors)
   local trs = {}
   for id, s in pairs(scores) do
      if table.HasValue(traitors, id) then
         if s.innos > 0 then
            table.insert(trs, id)
         end
      end
   end

   local choices = table.Count(trs)
   if choices > 0 then
      -- award a random killer
      local pick = math.random(1, choices)
      local sid64 = trs[pick]
      local nick = players[sid64]
      if not nick then return nil end

      local kills = scores[sid64].innos
      if kills == 1 then
         return {title=T("aw_nkt1_title"), nick=nick, text=T("aw_nkt1_text"), priority=0}
      elseif kills == 2 then
         return {title=T("aw_nkt2_title"), nick=nick, text=T("aw_nkt2_text"), priority=1}
      elseif kills == 3 then
         return {title=T("aw_nkt3_title"), nick=nick, text=T("aw_nkt3_text"), priority=kills}
      elseif kills >= 4 and kills < 7 then
         return {title=T("aw_nkt4_title"), nick=nick, text=PT("aw_nkt4_text", {num = kills}), priority=kills + 2}
      elseif kills >= 7 then
         return {title=T("aw_nkt5_title"), nick=nick, text=T("aw_nkt5_text"), priority=kills + 5}
      end
   else
      return nil
   end
end

local function NumKills_Inno(events, scores, players, traitors)
   local ins = {}
   for id, s in pairs(scores) do
      if not table.HasValue(traitors, id) then
         if s.traitors > 0 then
            table.insert(ins, id)
         end
      end
   end

   local choices = table.Count(ins)
   if not table.IsEmpty(ins) then
      -- award a random killer
      local pick = math.random(1, choices)
      local sid64 = ins[pick]
      local nick = players[sid64]
      if not nick then return nil end

      local kills = scores[sid64].traitors
      if kills == 1 then
         return {title=T("aw_nki1_title"), nick=nick, text=T("aw_nki1_text"), priority = 0}
      elseif kills == 2 then
         return {title=T("aw_nki2_title"), nick=nick, text=T("aw_nki2_text"), priority = 1}
      elseif kills == 3 then
         return {title=T("aw_nki3_title"), nick=nick, text=T("aw_nki3_text"), priority= 5}
      elseif kills >= 4 then
         return {title=T("aw_nki4_title"), nick=nick, text=T("aw_nki4_text"), priority=kills + 10}
      end
   else
      return nil
   end
end

local function FallDeath(events, scores, players, traitors)
   for k, e in pairs(events) do
      if e.id == EVENT_KILL and is_dmg(e.dmg.t, DMG_FALL) then
         if e.att.ni != "" then
            return {title=T("aw_fal1_title"), nick=e.att.ni, text=T("aw_fal1_text"), priority=math.random(7, 15)}
         else
            return {title=T("aw_fal2_title"), nick=e.vic.ni, text=T("aw_fal2_text"), priority=math.random(1, 5)}
         end
      end
   end

   return nil
end

local function FallKill(events, scores, players, traitors)
   for k, e in pairs(events) do
      if e.id == EVENT_KILL and is_dmg(e.dmg.t, DMG_CRUSH) and is_dmg(e.dmg.t, DMG_PHYSGUN) then
         if e.att.ni != "" then
            return {title=T("aw_fal3_title"), nick=e.att.ni, text=T("aw_fal3_text"), priority=math.random(10, 15)}
         end
      end
   end
end

local function Headshots(events, scores, players, traitors)
   local hs = {}
   for k, e in pairs(events) do
      if e.id == EVENT_KILL and e.dmg.h and is_dmg(e.dmg.t, DMG_BULLET) then
         hs[e.att.sid64] = (hs[e.att.sid64] or 0) + 1
      end
   end

   if table.IsEmpty(hs) then return nil end

   -- find the one with the most shots
   local m_id, m_num = FindHighest(hs)

   if not m_id then return nil end

   local nick = players[m_id]
   if not nick then return nil end

   local award = {nick=nick, priority=m_num / 2}
   if m_num > 1 and m_num < 4 then
      award.title = T("aw_hed1_title")
      award.text = PT("aw_hed1_text", {num = m_num})
   elseif m_num >= 4 and m_num < 6 then
      award.title = T("aw_hed2_title")
      award.text = PT("aw_hed2_text", {num = m_num})
   elseif m_num >= 6 then
      award.title = T("aw_hed3_title")
      award.text = PT("aw_hed3_text", {num = m_num})
      award.priority = m_num + 5
   else
      return nil
   end

   return award
end

local function UsedAmmoMost(events, ammotype)
   local user = {}
   for k, e in pairs(events) do
      if e.id == EVENT_KILL and e.dmg.g == ammotype then
         user[e.att.sid64] = (user[e.att.sid64] or 0) + 1
      end
   end

   if table.IsEmpty(user) then return nil end

   local m_id, m_num = FindHighest(user)

   if not m_id then return nil end

   return {sid64=m_id, kills=m_num}
end

local function CrowbarUser(events, scores, players, traitors)
   local most = UsedAmmoMost(events, AMMO_CROWBAR)
   if not most then return nil end

   local nick = players[most.sid64]
   if not nick then return nil end

   local award = {nick=nick, priority=most.kills + math.random(0, 4)}
   local kills = most.kills
   if kills > 1 and kills < 3 then
      award.title = T("aw_cbr1_title")
      award.text = PT("aw_cbr1_text", {num = kills})
   elseif kills >= 3 then
      award.title = T("aw_cbr2_title")
      award.text = PT("aw_cbr2_text", {num = kills})
      award.priority = kills + math.random(5, 10)
   else
      return nil
   end

   return award
end

local function PistolUser(events, scores, players, traitors)
   local most = UsedAmmoMost(events, AMMO_PISTOL)
   if not most then return nil end

   local nick = players[most.sid64]
   if not nick then return nil end

   local award = {nick=nick, priority=most.kills}
   local kills = most.kills
   if kills > 1 and kills < 4 then
      award.title = T("aw_pst1_title")
      award.text = PT("aw_pst1_text", {num = kills})
   elseif kills >= 4 then
      award.title = T("aw_pst2_title")
      award.text = PT("aw_pst2_text", {num = kills})
      award.priority = award.priority + math.random(0, 5)
   else
      return nil
   end

   return award
end

local function ShotgunUser(events, scores, players, traitors)
   local most = UsedAmmoMost(events, AMMO_SHOTGUN)
   if not most then return nil end

   local nick = players[most.sid64]
   if not nick then return nil end

   local award = {nick=nick, priority=most.kills}
   local kills = most.kills
   if kills > 1 and kills < 4 then
      award.title = T("aw_sgn1_title")
      award.text = PT("aw_sgn1_text", {num = kills})
      award.priority = math.Round(kills / 2)
   elseif kills >= 4 then
      award.title = T("aw_sgn2_title")
      award.text = PT("aw_sgn2_text", {num = kills})
   else
      return nil
   end

   return award
end

local function RifleUser(events, scores, players, traitors)
   local most = UsedAmmoMost(events, AMMO_RIFLE)
   if not most then return nil end

   local nick = players[most.sid64]
   if not nick then return nil end

   local award = {nick=nick, priority=most.kills}
   local kills = most.kills
   if kills > 1 and kills < 4 then
      award.title = T("aw_rfl1_title")
      award.text = PT("aw_rfl1_text", {num = kills})
      award.priority = math.Round(kills / 2)
   elseif kills >= 4 then
      award.title = T("aw_rfl2_title")
      award.text = PT("aw_rfl2_text", {num = kills})
   else
      return nil
   end

   return award
end

local function DeagleUser(events, scores, players, traitors)
   local most = UsedAmmoMost(events, AMMO_DEAGLE)
   if not most then return nil end

   local nick = players[most.sid64]
   if not nick then return nil end

   local award = {nick=nick, priority=most.kills}
   local kills = most.kills
   if kills > 1 and kills < 4 then
      award.title = T("aw_dgl1_title")
      award.text = PT("aw_dgl1_text", {num = kills})
      award.priority = math.Round(kills / 2)
   elseif kills >= 4 then
      award.title = T("aw_dgl2_title")
      award.text = PT("aw_dgl2_text", {num = kills})
      award.priority = kills + math.random(2, 6)
   else
      return nil
   end

   return award
end

local function MAC10User(events, scores, players, traitors)
   local most = UsedAmmoMost(events, AMMO_MAC10)
   if not most then return nil end

   local nick = players[most.sid64]
   if not nick then return nil end

   local award = {nick=nick, priority=most.kills}
   local kills = most.kills
   if kills > 1 and kills < 4 then
      award.title = T("aw_mac1_title")
      award.text = PT("aw_mac1_text", {num = kills})
      award.priority = math.Round(kills / 2)
   elseif kills >= 4 then
      award.title = T("aw_mac2_title")
      award.text = PT("aw_mac2_text", {num = kills})
   else
      return nil
   end

   return award
end

local function SilencedPistolUser(events, scores, players, traitors)
   local most = UsedAmmoMost(events, AMMO_SIPISTOL)
   if not most then return nil end

   local nick = players[most.sid64]
   if not nick then return nil end

   local award = {nick=nick, priority=most.kills}
   local kills = most.kills
   if kills > 1 and kills < 3 then
      award.title = T("aw_sip1_title")
      award.text = PT("aw_sip1_text", {num = kills})
   elseif kills >= 3 then
      award.title = T("aw_sip2_title")
      award.text = PT("aw_sip2_text", {num = kills})
   else
      return nil
   end

   return award
end

local function KnifeUser(events, scores, players, traitors)
   local most = UsedAmmoMost(events, AMMO_KNIFE)
   if not most then return nil end

   local nick = players[most.sid64]
   if not nick then return nil end

   local award = {nick=nick, priority=most.kills}
   local kills = most.kills

   if kills == 1 then
      if table.HasValue(traitors, most.sid64) then
         award.title = T("aw_knf1_title")
         award.text = PT("aw_knf1_text", {num = kills})
         award.priority = 0
      else
         award.title = T("aw_knf2_title")
         award.text = PT("aw_knf2_text", {num = kills})
         award.priority = 5
      end
   elseif kills > 1 and kills < 4 then
      award.title = T("aw_knf3_title")
      award.text = PT("aw_knf3_text", {num = kills})
   elseif kills >= 4 then
      award.title = T("aw_knf4_title")
      award.text = PT("aw_knf4_text", {num = kills})
   else
      return nil
   end

   return award
end

local function FlareUser(events, scores, players, traitors)
   local most = UsedAmmoMost(events, AMMO_FLARE)
   if not most then return nil end

   local nick = players[most.sid64]
   if not nick then return nil end

   local award = {nick=nick, priority=most.kills}
   local kills = most.kills
   if kills > 1 and kills < 3 then
      award.title = T("aw_flg1_title")
      award.text = PT("aw_flg1_text", {num = kills})
   elseif kills >= 3 then
      award.title = T("aw_flg2_title")
      award.text = PT("aw_flg2_text", {num = kills})
   else
      return nil
   end

   return award
end

local function M249User(events, scores, players, traitors)
   local most = UsedAmmoMost(events, AMMO_M249)
   if not most then return nil end

   local nick = players[most.sid64]
   if not nick then return nil end

   local award = {nick=nick, priority=most.kills}
   local kills = most.kills
   if kills > 1 and kills < 4 then
      award.title = T("aw_hug1_title")
      award.text = PT("aw_hug1_text", {num = kills})
   elseif kills >= 4 then
      award.title = T("aw_hug2_title")
      award.text = PT("aw_hug2_text", {num = kills})
   else
      return nil
   end

   return award
end

local function M16User(events, scores, players, traitors)
   local most = UsedAmmoMost(events, AMMO_M16)
   if not most then return nil end

   local nick = players[most.sid64]
   if not nick then return nil end

   local award = {nick=nick, priority=most.kills}
   local kills = most.kills
   if kills > 1 and kills < 4 then
      award.title = T("aw_msx1_title")
      award.text = PT("aw_msx1_text", {num = kills})
   elseif kills >= 4 then
      award.title = T("aw_msx2_title")
      award.text = PT("aw_msx2_text", {num = kills})
   else
      return nil
   end

   return award
end

local function TeamKiller(events, scores, players, traitors)
   local num_traitors = table.Count(traitors)
   local num_inno = table.Count(players) - num_traitors

   -- find biggest tker
   local tker = nil
   local pct = 0
   for id, s in pairs(scores) do
      local kills = s.innos
      local team = num_inno - 1
      if table.HasValue(traitors, id) then
         kills = s.traitors
         team = num_traitors - 1
      end

      if kills > 0 and (kills / team) > pct then
         pct = kills / team
         tker = id
      end
   end

   -- no tks
   if pct == 0 or tker == nil then return nil end

   local nick = players[tker]
   if not nick then return nil end

   local was_traitor = table.HasValue(traitors, tker)
   local kills = (was_traitor and scores[tker].traitors > 0 and scores[tker].traitors) or (scores[tker].innos > 0 and scores[tker].innos) or 0
   local award = {nick=nick, priority=kills}
   if kills == 1 then
      award.title = T("aw_tkl1_title")
      award.text =  T("aw_tkl1_text")
      award.priority = 0
   elseif kills == 2 then
      award.title = T("aw_tkl2_title")
      award.text =  T("aw_tkl2_text")
   elseif kills == 3 then
      award.title = T("aw_tkl3_title")
      award.text =  T("aw_tkl3_text")
   elseif pct >= 1.0 then
      award.title = T("aw_tkl4_title")
      award.text =  T("aw_tkl4_text")
      award.priority = kills + math.random(3, 6)
   elseif pct >= 0.75 and not was_traitor then
      award.title = T("aw_tkl5_title")
      award.text =  T("aw_tkl5_text")
      award.priority = kills + 10
   elseif pct > 0.5 then
      award.title = T("aw_tkl6_title")
      award.text =  T("aw_tkl6_text")
      award.priority = kills + math.random(2, 7)
   elseif pct >= 0.25 then
      award.title = T("aw_tkl7_title")
      award.text =  T("aw_tkl7_text")
   else
      return nil
   end
   return award
end

local function Burner(events, scores, players, traitors)
   local brn = {}
   for k, e in pairs(events) do
      if e.id == EVENT_KILL and is_dmg(e.dmg.t, DMG_BURN) then
         brn[e.att.sid64] = (brn[e.att.sid64] or 0) + 1
      end
   end

   if table.IsEmpty(brn) then return nil end

   -- find the one with the most burnings
   local m_id, m_num = FindHighest(brn)

   if not m_id then return nil end

   local nick = players[m_id]
   if not nick then return nil end

   local award = {nick=nick, priority=m_num * 2}
   if m_num > 1 and m_num < 4 then
      award.title = T("aw_brn1_title")
      award.text =  T("aw_brn1_text")
   elseif m_num >= 4 and m_num < 7 then
      award.title = T("aw_brn2_title")
      award.text =  T("aw_brn2_text")
   elseif m_num >= 7 then
      award.title = T("aw_brn3_title")
      award.text =  T("aw_brn3_text")
      award.priority = m_num + math.random(0, 4)
   else
      return nil
   end

   return award
end

local function Coroner(events, scores, players, traitors)
   local finders = {}
   for k, e in pairs(events) do
      if e.id == EVENT_BODYFOUND then
         finders[e.sid64] = (finders[e.sid64] or 0) + 1
      end
   end

   if table.IsEmpty(finders) then return end

   local m_id, m_num = FindHighest(finders)

   if not m_id then return nil end

   local nick = players[m_id]
   if not nick then return nil end

   local award = {nick=nick, priority=m_num}
   if m_num > 2 and m_num < 6 then
      award.title =  T("aw_fnd1_title")
      award.text =  PT("aw_fnd1_text", {num = m_num})
   elseif m_num >= 6 and m_num < 10 then
      award.title = T("aw_fnd2_title")
      award.text = PT("aw_fnd2_text", {num = m_num})
   elseif m_num >= 10 then
      award.title = T("aw_fnd3_title")
      award.text = PT("aw_fnd3_text", {num = m_num})
      award.priority = m_num + math.random(0, 4)
   else
      return nil
   end

   return award
end

local function CreditFound(events, scores, players, traitors)
   local finders = {}
   for k, e in pairs(events) do
      if e.id == EVENT_CREDITFOUND then
         finders[e.sid64] = (finders[e.sid64] or 0) + e.cr
      end
   end

   if table.IsEmpty(finders) then return end

   local m_id, m_num = FindHighest(finders)

   if not m_id then return nil end

   local nick = players[m_id]
   if not nick then return nil end

   local award = {nick=nick}
   if m_num > 2 then
      award.title = T("aw_crd1_title")
      award.text = PT("aw_crd1_text", {num = m_num})
      award.priority = m_num + math.random(0, m_num)
   else
      return nil
   end

   return award
end

local function TimeOfDeath(events, scores, players, traitors)
   local near = 10
   local time_near_start = CLSCORE.StartTime + near
   local time_near_end   = nil

   local traitor_win = nil

   local e = nil
   for i=#events, 1, -1 do
      e = events[i]

      if e.id == EVENT_FINISH then
         time_near_end = e.t - near
         traitor_win = (e.win == WIN_TRAITOR)

      elseif e.id == EVENT_KILL and e.vic then

         if time_near_end and
            e.t > time_near_end and e.vic.tr == traitor_win then
            return {
               nick  = e.vic.ni,
               title = T("aw_tod1_title"),
               text  = T("aw_tod1_text"),
               priority = (e.t - time_near_end) * 2
            };

         elseif e.t < time_near_start then
            return {
               nick  = e.vic.ni,
               title = T("aw_tod2_title"),
               text  = T("aw_tod2_text"),
               priority = (time_near_start - e.t) * 2
            };
         end
      end
   end
end


-- New award functions must be added to this to be used by CLSCORE.
-- Note that AWARDS is global. You can just go: table.insert(AWARDS, myawardfn) in your SWEPs.
AWARDS = { FirstSuicide, ExplosiveGrant, ExplodedSelf, FirstBlood, AllKills, NumKills_Traitor, NumKills_Inno, FallDeath, Headshots, PistolUser, ShotgunUser, RifleUser, DeagleUser, MAC10User, CrowbarUser, TeamKiller, Burner, SilencedPistolUser, KnifeUser, FlareUser, Coroner, M249User, M16User, CreditFound, FallKill, TimeOfDeath }
