-- DNA Scanner

AddCSLuaFile()

DEFINE_BASECLASS "weapon_tttbase"

SWEP.HoldType              = "normal"

if CLIENT then
   SWEP.PrintName          = "dna_name"
   SWEP.Slot               = 8

   SWEP.ViewModelFOV       = 10
   SWEP.DrawCrosshair      = false

   SWEP.EquipMenuData = {
      type = "item_weapon",
      desc = "dna_desc"
   };

   SWEP.Icon               = "vgui/ttt/icon_wtester"
end

SWEP.Base                  = "weapon_tttbase"

SWEP.ViewModel             = "models/weapons/v_crowbar.mdl"
SWEP.WorldModel            = "models/props_lab/huladoll.mdl"

SWEP.Primary.ClipSize      = -1
SWEP.Primary.DefaultClip   = -1
SWEP.Primary.Automatic     = false
SWEP.Primary.Delay         = 1
SWEP.Primary.Ammo          = "none"

SWEP.Secondary.ClipSize    = -1
SWEP.Secondary.DefaultClip = -1
SWEP.Secondary.Automatic   = false
SWEP.Secondary.Ammo        = "none"
SWEP.Secondary.Delay       = 2

SWEP.Kind                  = WEAPON_ROLE
SWEP.CanBuy                = nil -- no longer a buyable thing
SWEP.WeaponID              = AMMO_WTESTER

SWEP.InLoadoutFor          = {ROLE_DETECTIVE}

--SWEP.AllowDrop = false
SWEP.AutoSpawnable         = false
SWEP.NoSights              = true

SWEP.Range                 = 175

SWEP.ItemSamples           = {}

SWEP.NowRepeating          = nil

local MAX_ITEM = 30
SWEP.MaxItemSamples        = MAX_ITEM

local CHARGE_DELAY = 0.1
local CHARGE_RATE = 3
local MAX_CHARGE = 1250

local SAMPLE_PLAYER = 1
local SAMPLE_ITEM   = 2

AccessorFuncDT(SWEP, "charge", "Charge")
AccessorFuncDT(SWEP, "last_scanned", "LastScanned")

if CLIENT then
   CreateClientConVar("ttt_dna_scan_repeat", 1, true, true)
else
   function SWEP:GetRepeating()
      local ply = self:GetOwner()
      return IsValid(ply) and ply:GetInfoNum("ttt_dna_scan_repeat", 1) == 1
   end
end

SWEP.NextCharge = 0

function SWEP:SetupDataTables()
   self:DTVar("Int", 0, "charge")
   self:DTVar("Int", 1, "last_scanned")

   return self.BaseClass.SetupDataTables(self)
end

function SWEP:Initialize()
   self:SetCharge(MAX_CHARGE)
   self:SetLastScanned(-1)

   if CLIENT then
      self:AddHUDHelp("dna_help_primary", "dna_help_secondary", true)
   end

   return self.BaseClass.Initialize(self)
end

local beep_miss = Sound("player/suit_denydevice.wav")
function SWEP:PrimaryAttack()
   self:SetNextPrimaryFire( CurTime() + self.Primary.Delay )

   -- will be tracing against players
   self:GetOwner():LagCompensation(true)

   local spos = self:GetOwner():GetShootPos()
   local sdest = spos + (self:GetOwner():GetAimVector() * self.Range)

   local tr = util.TraceLine({start=spos, endpos=sdest, filter=self:GetOwner(), mask=MASK_SHOT})
   local ent = tr.Entity
   if IsValid(ent) and (not ent:IsPlayer()) then
      if SERVER then
         if ent:GetClass() == "prop_ragdoll" and ent.killer_sample then
            if CORPSE.GetFound(ent, false) then
               self:GatherRagdollSample(ent)
            else
               self:Report("dna_identify")
            end
         elseif ent.fingerprints and #ent.fingerprints > 0 then
            self:GatherObjectSample(ent)
         else
            self:Report("dna_notfound")
         end
      end
   else
      if CLIENT then
         self:GetOwner():EmitSound(beep_miss)
      end
   end

   self:GetOwner():LagCompensation(false)
end

function SWEP:GatherRagdollSample(ent)
   local sample = ent.killer_sample or {t=0, killer=nil}
   local ply = sample.killer
   if (not IsValid(ply)) and sample.killer_sid64 then
      ply = player.GetBySteamID64(sample.killer_sid64)
   end


   if IsValid(ply) then
      if sample.t < CurTime() then
         self:Report("dna_decayed")
         return
      end

      local added = self:AddPlayerSample(ent, ply)

      if not added then
         self:Report("dna_limit")
      else
         self:Report("dna_killer")

         if self:GetRepeating() and self:GetCharge() == MAX_CHARGE then
            self:PerformScan(#self.ItemSamples)
         end
      end
   elseif ply != nil then
      -- not valid but not nil -> disconnected?
      self:Report("dna_no_killer")
   else
      self:Report("dna_notfound")
   end
end

function SWEP:GatherObjectSample(ent)
   if ent:GetClass() == "ttt_c4" and ent:GetArmed() then
      self:Report("dna_armed")
   else
      local collected, old, own = self:AddItemSample(ent)

      if collected == -1 then
         self:Report("dna_limit")
      else
         self:Report("dna_object", {num = collected})
      end
   end
end

function SWEP:Report(msg, params)
   LANG.Msg(self:GetOwner(), msg, params)
end

function SWEP:AddPlayerSample(corpse, killer)
   if #self.ItemSamples < self.MaxItemSamples then
      local prnt = {source=corpse, ply=killer, type=SAMPLE_PLAYER, cls=killer:GetClass()}
      if not table.HasTable(self.ItemSamples, prnt) then
         table.insert(self.ItemSamples, prnt)

         DamageLog("SAMPLE:\t " .. self:GetOwner():Nick() .. " retrieved DNA of " .. (IsValid(killer) and killer:Nick() or "<disconnected>") .. " from corpse of " .. (IsValid(corpse) and CORPSE.GetPlayerNick(corpse) or "<invalid>"))

         hook.Call("TTTFoundDNA", GAMEMODE, self:GetOwner(), killer, corpse)
      end
      return true
   end
   return false
end

function SWEP:AddItemSample(ent)
   if #self.ItemSamples < self.MaxItemSamples then
      table.Shuffle(ent.fingerprints)

      local new = 0
      local old = 0
      local own = 0
      for _, p in pairs(ent.fingerprints) do
         local prnt = {source=ent, ply=p, type=SAMPLE_ITEM, cls=ent:GetClass()}

         if p == self:GetOwner() then
            own = own + 1
         elseif table.HasTable(self.ItemSamples, prnt) then
            old = old + 1
         else
            table.insert(self.ItemSamples, prnt)

            DamageLog("SAMPLE:\t " .. self:GetOwner():Nick() .. " retrieved DNA of " .. (IsValid(p) and p:Nick() or "<disconnected>") .. " from " .. ent:GetClass())

            new = new + 1
            hook.Call("TTTFoundDNA", GAMEMODE, self:GetOwner(), p, ent)
         end
      end
      return new, old, own
   end
   return -1
end

function SWEP:RemoveItemSample(idx)
   if self.ItemSamples[idx] then
      if self:GetLastScanned() == idx then
         self:ClearScanState()
      end

      table.remove(self.ItemSamples, idx)
      
      self:SendPrints(false)
   end   
end

function SWEP:SecondaryAttack()
   self:SetNextSecondaryFire( CurTime() + 0.05 )

   if CLIENT then return end

   self:SendPrints(true)
end

if SERVER then
   -- Sending this all in one umsg limits the max number of samples. 17 player
   -- samples and 20 item samples (with 20 matches) has been verified as
   -- working in the old DNA sampler.
   function SWEP:SendPrints(should_open)
      net.Start("TTT_ShowPrints", self:GetOwner())
        net.WriteBit(should_open)
        net.WriteUInt(#self.ItemSamples, 8)

        for k, v in ipairs(self.ItemSamples) do
          net.WriteString(v.cls)
        end

      net.Send(self:GetOwner())
   end

   function SWEP:SendScan(pos)
      local clear = (pos == nil) or (not IsValid(self:GetOwner()))
      net.Start("TTT_ScanResult", self:GetOwner())
        net.WriteBit(clear)
        if not clear then
          net.WriteVector(pos)
        end
      net.Send(self:GetOwner())
   end

   function SWEP:ClearScanState()
      self:SetLastScanned(-1)
      self.NowRepeating = nil
      self:SendScan(nil)
   end

   local function GetScanTarget(sample)
      if not sample then return end

      local target = sample.ply
      if not IsValid(target) then return end

      -- decoys always take priority, even after death
      if IsValid(target.decoy) then
         target = target.decoy
      elseif not target:IsTerror() then
         -- fall back to ragdoll, as long as it's not destroyed
         target = target.server_ragdoll
         if not IsValid(target) then return end
      end

      return target
   end

   function SWEP:PerformScan(idx, repeated)
      if self:GetCharge() < MAX_CHARGE then return end

      local sample = self.ItemSamples[idx]
      if (not sample) or (not IsValid(self:GetOwner())) then
         if repeated then self:ClearScanState() end
         return
      end

      local target = GetScanTarget(sample)
      if not IsValid(target) then
         self:Report("dna_gone")
         self:SetCharge(self:GetCharge() - 50)

         if repeated then self:ClearScanState() end
         return
      end

      local pos = target:LocalToWorld(target:OBBCenter())

      self:SendScan(pos)
      
      self:SetLastScanned(idx)
      self.NowRepeating = self:GetRepeating()

      local dist = math.ceil(self:GetOwner():GetPos():Distance(pos))

      self:SetCharge(math.max(0, self:GetCharge() - math.max(50, dist / 2)))
   end

   function SWEP:Think()
      if self:GetCharge() < MAX_CHARGE then
         if self.NextCharge < CurTime() then
            self:SetCharge(math.min(MAX_CHARGE, self:GetCharge() + CHARGE_RATE))

            self.NextCharge = CurTime() + CHARGE_DELAY
         end
      elseif self.NowRepeating and IsValid(self:GetOwner()) then
         -- owner changed his mind since running last scan?
         if self:GetRepeating() then 
            self:PerformScan(self:GetLastScanned(), true)
         else
            self.NowRepeating = self:GetRepeating()
         end
      end

      return true
   end
end

-- Helper to get at a player's scanner, if he has one
local function GetTester(ply)
   if IsValid(ply) then
      local tester = ply:GetActiveWeapon()
      if IsValid(tester) and tester:GetClass() == "weapon_ttt_wtester" then
         return tester
      end
   end
   return nil
end


if CLIENT then

   local T = LANG.GetTranslation
   local PT = LANG.GetParamTranslation
   local TT = LANG.TryTranslation
   
   function SWEP:DrawHUD()
      self:DrawHelp()

      local spos = self:GetOwner():GetShootPos()
      local sdest = spos + (self:GetOwner():GetAimVector() * self.Range)

      local tr = util.TraceLine({start=spos, endpos=sdest, filter=self:GetOwner(), mask=MASK_SHOT})

      local length = 20
      local gap = 6

      local can_sample = false

      local ent = tr.Entity
      if IsValid(ent) then
         -- weapon or dropped equipment
         if ((ent:IsWeapon() or ent.CanHavePrints) or
             -- knife in corpse, or a ragdoll
             ent:GetNWBool("HasPrints", false) or
             (ent:GetClass() == "prop_ragdoll" and
              CORPSE.GetPlayerNick(ent, false) and
              CORPSE.GetFound(ent, false))) then

            surface.SetDrawColor(0, 255, 0, 255)
            gap = 0

            can_sample = true
         else
            surface.SetDrawColor(255, 0, 0, 200)
            gap = 0
         end
      else
         surface.SetDrawColor(255, 255, 255, 200)
      end

      local x = ScrW() / 2.0
      local y = ScrH() / 2.0

      surface.DrawLine( x - length, y, x - gap, y )
      surface.DrawLine( x + length, y, x + gap, y )
      surface.DrawLine( x, y - length, x, y - gap )
      surface.DrawLine( x, y + length, x, y + gap )

      if ent and can_sample then
         surface.SetFont("DefaultFixedDropShadow")
         surface.SetTextColor(0, 255, 0, 255)
         surface.SetTextPos( x + length*2, y - length*2 )
         surface.DrawText(T("dna_hud_type") .. ": " .. (ent:GetClass() == "prop_ragdoll" and T("dna_hud_body") or T("dna_hud_item")))
         surface.SetTextPos( x + length*2, y - length*2 + 15)
         surface.DrawText("ID:   #" .. ent:EntIndex())
      end
   end

   local basedir = "vgui/ttt/icon_"
   local function GetDisplayData(cls)
      local wep = util.WeaponForClass(cls)

      local img = basedir .. "nades"
      local name = "something"

      if cls == "player" then
         img  = basedir .. "corpse"
         name = "corpse"
      elseif wep then
         img  = wep.Icon      or img
         name = wep.PrintName or name
      end

      return img, name
   end

   local last_panel_selected = 1
   local function ShowPrintsPopup(item_prints, tester)
      local m = 10
      local bw, bh = 100, 25

      local dpanel = vgui.Create("DFrame")
      local w, h = 400, 250
      dpanel:SetSize(w, h)

      dpanel:AlignRight(5)
      dpanel:AlignBottom(5)

      dpanel:SetTitle(T("dna_menu_title"))
      dpanel:SetVisible(true)
      dpanel:ShowCloseButton(true)
      dpanel:SetMouseInputEnabled(true)

      local wrap = vgui.Create("DPanel", dpanel)
      wrap:StretchToParent(m/2, m + 15, m/2, m + bh)
      wrap:SetPaintBackground(false)

      -- item sample listing
      local ilist = vgui.Create("DPanelSelect", wrap)
      ilist:StretchToParent(0,0,0,0)
      ilist:EnableHorizontal(true)
      ilist:SetSpacing(1)
      ilist:SetPadding(1)

      ilist.OnActivePanelChanged = function(s, old, new)
                                      last_panel_selected = new and new.key or 1
                                   end

      ilist.OnScan = function(s, scanned_pnl)
                        for k, pnl in pairs(s:GetItems()) do
                           pnl:SetIconColor(COLOR_LGRAY)
                        end

                        scanned_pnl:SetIconColor(COLOR_WHITE)
                     end

      if ilist.VBar then 
         ilist.VBar:Remove()
         ilist.VBar = nil
      end

      local iscroll = vgui.Create("DHorizontalScroller", ilist)
      iscroll:SetPos(3,1)
      iscroll:SetSize(363, 66)
      iscroll:SetOverlap(1)

      iscroll.LoadFrom = function(s, tbl, layout)
                            ilist:Clear(true)
                            ilist.SelectedPanel = nil

                            -- Scroller has no Clear()
                            for k, pnl in pairs(s.Panels) do
                               if IsValid(pnl) then
                                  pnl:Remove()
                               end
                            end

                            s.Panels = {}

                            local last_scan = tester and tester:GetLastScanned() or -1

                            for k, v in ipairs(tbl) do
                               local ic = vgui.Create("SimpleIcon", ilist)

                               ic:SetIconSize(64)

                               local img, name = GetDisplayData(v)

                               ic:SetIcon(img)

                               local tip = PT("dna_menu_sample", {source = TT(name) or "???"})

                               ic:SetTooltip(tip)

                               ic.key = k
                               ic.val = v

                               if layout then
                                  ic:PerformLayout()
                               end

                               ilist:AddPanel(ic)
                               s:AddPanel(ic)

                               if k == last_panel_selected then
                                  ilist:SelectPanel(ic)
                               end

                               if last_scan > 0 then
                                  ic:SetIconColor(last_scan == k and COLOR_WHITE or COLOR_LGRAY)
                               end
                            end

                            iscroll:InvalidateLayout()
                         end

      iscroll:LoadFrom(item_prints)

      local delwrap = vgui.Create("DPanel", wrap)
      delwrap:SetPos(m, 70)
      delwrap:SetSize(370, bh)
      delwrap:SetPaintBackground(false)

      local delitem = vgui.Create("DButton", delwrap)
      delitem:SetPos(0,0)
      delitem:SetSize(bw, bh)
      delitem:SetText(T("dna_menu_remove"))
      delitem.DoClick = function()
                           if IsValid(ilist) and IsValid(ilist.SelectedPanel) then
                              local idx = ilist.SelectedPanel.key
                              RunConsoleCommand("ttt_wtester_remove", idx)
                           end
                        end

      delitem.Think = function(s)
                         if IsValid(ilist) and IsValid(ilist.SelectedPanel) then
                            s:SetEnabled(true)
                         else
                            s:SetEnabled(false)
                         end
                      end

      local delhlp = vgui.Create("DLabel", delwrap)
      delhlp:SetPos(bw + m, 0)
      delhlp:SetText(T("dna_menu_help1"))
      delhlp:SizeToContents()


      -- hammer out layouts
      wrap:PerformLayout()
      -- scroller needs to sort itself out so it displays all icons it should
      iscroll:PerformLayout()

      local mwrap = vgui.Create("DPanel", wrap)
      mwrap:SetPaintBackground(false)
      mwrap:SetPos(m,100)
      mwrap:SetSize(370, 90)

      
      local bar = vgui.Create("TTTProgressBar", mwrap)
      bar:SetSize(370, 35)
      bar:SetPos(0, 0)
      bar:CenterHorizontal()
      bar:SetMin(0)
      bar:SetMax(MAX_CHARGE)
      bar:SetValue(tester and math.min(MAX_CHARGE, tester:GetCharge()))
      bar:SetColor(COLOR_GREEN)
      bar:LabelAsPercentage()

      local state = vgui.Create("DLabel", bar)
      state:SetSize(0, 35)
      state:SetPos(10, 6)
      state:SetFont("Trebuchet22")
      state:SetText(T("dna_menu_ready"))
      state:SetTextColor(COLOR_WHITE)
      state:SizeToContents()

      local scan = vgui.Create("DButton", mwrap)
      scan:SetText(T("dna_menu_scan"))
      scan:SetSize(bw, bh)
      scan:SetPos(0, 40)
      scan:SetEnabled(false)

      scan.DoClick = function(s)
                         if IsValid(ilist) then
                            local i = ilist.SelectedPanel
                            if IsValid(i) then
                               RunConsoleCommand("ttt_wtester_scan", i.key)

                               ilist:OnScan(i)
                            end
                         end
                      end

      local dcheck = vgui.Create("DCheckBoxLabel", mwrap)
      dcheck:SetPos(0, 70)
      dcheck:SetText(T("dna_menu_repeat"))
      dcheck:SetIndent(7)
      dcheck:SizeToContents()
      dcheck:SetConVar("ttt_dna_scan_repeat")
      --dcheck:SetValue(tester and tester:GetRepeating())


      local scanhlp = vgui.Create("DLabel", mwrap)
      scanhlp:SetPos(bw + m, 40)
      scanhlp:SetText(T("dna_menu_help2"))
      scanhlp:SizeToContents()

      -- CLOSE
      local dbut = vgui.Create("DButton", dpanel)
      dbut:SetSize(bw, bh)
      dbut:SetPos(m, h - bh - m/1.5)
      dbut:CenterHorizontal()
      dbut:SetText(T("close"))
      dbut.DoClick = function() dpanel:Close() end

      dpanel:MakePopup()
      dpanel:SetKeyboardInputEnabled(false)

      -- Expose updating fns
      dpanel.UpdatePrints = function(s, its)
                               if IsValid(iscroll) then
                                  iscroll:LoadFrom(its)
                               end
                            end

      dpanel.Think = function(s)
                        if IsValid(bar) and IsValid(scan) and tester then
                           local charge = tester:GetCharge()
                           bar:SetValue(math.min(MAX_CHARGE, charge))
                           if charge < MAX_CHARGE then
                              bar:SetColor(COLOR_RED)

                              state:SetText(T("dna_menu_charge"))
                              state:SizeToContents()

                              scan:SetEnabled(false)
                           else
                              bar:SetColor(COLOR_GREEN)

                              if IsValid(ilist) and IsValid(ilist.SelectedPanel) then
                                 scan:SetEnabled(true)

                                 state:SetText(T("dna_menu_ready"))
                                 state:SizeToContents()
                              else
                                 state:SetText(T("dna_menu_select"))
                                 state:SizeToContents()
                                 scan:SetEnabled(false)
                              end
                           end
                        end
                     end

      return dpanel
   end

   local printspanel = nil
   local function RecvPrints()
      local should_open = net.ReadBit() == 1

      local num = net.ReadUInt(8)
      local item_prints = {}
      for i=1, num do
         local ent = net.ReadString()
         table.insert(item_prints, ent)
      end

      if should_open then
         if IsValid(printspanel) then
            printspanel:Remove()
         end

         local tester = GetTester(LocalPlayer())

         printspanel = ShowPrintsPopup(item_prints, tester)
      else
         if IsValid(printspanel) then
            printspanel:UpdatePrints(item_prints)
         end
      end
   end
   net.Receive("TTT_ShowPrints", RecvPrints)

   local beep_success = Sound("buttons/blip2.wav")
   local function RecvScan()
      local clear = net.ReadBit() == 1
      if clear then
         RADAR.samples = {}
         RADAR.samples_count = 0
         return
      end

      local target_pos = net.ReadVector()
      if not target_pos then return end

      RADAR.samples = {
         {pos = target_pos}
      };

      RADAR.samples_count = 1

      surface.PlaySound(beep_success)
   end
   net.Receive("TTT_ScanResult", RecvScan)

   function SWEP:ClosePrintsPanel()
      if IsValid(printspanel) then
         printspanel:Close()
      end
   end

else -- SERVER

   local function ScanPrint(ply, cmd, args)
      if #args != 1 then return end

      local tester = GetTester(ply)
      if IsValid(tester) then
         local i = tonumber(args[1])

         if i then
            tester:PerformScan(i)
         end
      end
   end
   concommand.Add("ttt_wtester_scan", ScanPrint)

   local function RemoveSample(ply, cmd, args)
      if #args != 1 then return end

      local idx = tonumber(args[1])
      if not idx then return end

      local tester = GetTester(ply)
      if IsValid(tester) then
         tester:RemoveItemSample(idx)
      end
   end
   concommand.Add("ttt_wtester_remove", RemoveSample)

end

function SWEP:OnRemove()
   if CLIENT then
      self:ClosePrintsPanel()
   end
end

function SWEP:OnDrop()
end

function SWEP:PreDrop()
   if IsValid(self:GetOwner()) then
      self:GetOwner().scanner_weapon = nil
   end
end

function SWEP:Reload()
   return false
end

function SWEP:Deploy()
   if SERVER and IsValid(self:GetOwner()) then
      self:GetOwner():DrawViewModel(false)
      self:GetOwner().scanner_weapon = self
   end
   return true
end

if CLIENT then
   function SWEP:DrawWorldModel()
      if not IsValid(self:GetOwner()) then
         self:DrawModel()
      end
   end
end
