--
-- (C) 2013-22 - ntop.org
--
local dirs = ntop.getDirs()
package.path = dirs.installdir .. "/scripts/lua/modules/?.lua;" .. package.path

require "lua_utils"
local rest_utils = require("rest_utils")
local callback_utils = require("callback_utils")
local hosts_map_utils = require("hosts_map_utils")

local HostsMapMode = hosts_map_utils.HostsMapMode
local MODES = hosts_map_utils.MODES

local MAX_RADIUS_PX = 30
local MIN_RADIUS_PX = 3

local rc = rest_utils.consts.success.ok
local first_host_table = {}
local second_host_table = {}
local max_radius = 0

-- Note To change the label "Remote Hosts" and "Local Hosts" shown
-- into the chart, just change the value of these names
local first_table_name = "Local Hosts"
local second_table_name = "Remote Hosts"
-- Change check_condition if you want a different condition to put hosts into the tables
local check_condition
local formatHost

local bubble_mode = tonumber(_GET["bubble_mode"]) or 0
local show_remote = _GET["show_remote"] or true

local ifid = _GET["ifid"]

if isEmptyString(ifid) then
   rc = rest_utils.consts.err.invalid_interface
   rest_utils.answer(rc)
   return
end

interface.select(ifid)

-- ###################################################

local function shrinkTable(t, max_num)
   local n = 1
   local t2 = {}
   
   for i,v in pairsByField(t, 'z', rev) do
      if(n < max_num) then
        t2[n] = v
        n = n + 1
      end
   end

   return(t2)
end

-- List of the functions used to get the necessary functions
-- ###################################################

local function allFlows(hostname, host, label)
   local line
   
   line = {
      meta = {
	 url_query = "host="..hostname,
	 label = label,
      },
      x = host["active_flows.as_server"],
      y = host["active_flows.as_client"],
      z = host["bytes.sent"] + host["bytes.rcvd"]
   }      

   return line
end

-- ###################################################

local function unreachableFlows(hostname, host, label)
   local line
   
   if (host["unreachable_flows.as_server"] + host["unreachable_flows.as_client"] > 0) then
      line = {
	 meta = {
	    url_query = "host="..hostname,
	    label = label,
	 },
	 x = host["unreachable_flows.as_server"],
	 y = host["unreachable_flows.as_client"],
	 z = host["bytes.sent"] + host["bytes.rcvd"]
      }
   end
   
   return line
end

-- ###################################################

local function alertedFlows(hostname, host, label)
   local line
   
   if ((host["alerted_flows.as_server"] ~= nil) and
       (host["alerted_flows.as_client"] ~= nil) and
       (host["alerted_flows.as_server"] + host["alerted_flows.as_client"] > 0)) then

      line = {
	 meta = {
	    url_query = "host="..hostname,
	    label = label,
	 },
	 x = host["alerted_flows.as_server"],
	 y = host["alerted_flows.as_client"],
	 z = host["alerted_flows.as_server"] + host["alerted_flows.as_client"]
      }
   end

   return line
end

-- ###################################################

local function dnsQueries(hostname, host, label)
   local line
   
   if ((host["dns"] ~= nil) and
      ((host["dns"]["sent"]["num_queries"] + host["dns"]["rcvd"]["num_queries"]) > 0)) then
      local x = host["dns"]["rcvd"]["num_replies_ok"]
      local y = host["dns"]["sent"]["num_queries"]
      
      line = {
	 meta = {
	    url_query = "host="..hostname,
	    label = label,
	 }, 
	 x = x, 
	 y = y, 
	 z = x + y
      }
   end

   return line
end

-- ###################################################

local function synDistribution(hostname, host, label)
   local line
   local stats = interface.getHostInfo(host["ip"], host["vlan"])
   
   line = {
      meta = {
	 url_query = "host="..hostname,
	 label = label,
      },
      x = stats["pktStats.sent"]["tcp_flags"]["syn"],
      y = stats["pktStats.recv"]["tcp_flags"]["syn"],
      z = host["active_flows.as_client"] + host["active_flows.as_server"]
   }

   return line
end

-- ###################################################

local function synVsRst(hostname, host, label)
   local line
   local stats = interface.getHostInfo(host["ip"], host["vlan"])
   
   line = {
      meta = {
	 url_query = "host="..hostname,
	 label = label,
      },
      x = stats["pktStats.sent"]["tcp_flags"]["syn"],
      y = stats["pktStats.recv"]["tcp_flags"]["rst"],
      z = host["active_flows.as_client"] + host["active_flows.as_server"]
   }

   return line
end

-- ###################################################

local function synVsSynack(hostname, host, label)
   local line
   local stats = interface.getHostInfo(host["ip"], host["vlan"])
   
   line = {
      meta = {
	 url_query = "host="..hostname,
	 label = label,
      },
      x = stats["pktStats.sent"]["tcp_flags"]["syn"],
      y = stats["pktStats.recv"]["tcp_flags"]["synack"],
      z = host["active_flows.as_client"] + host["active_flows.as_server"]
   }

   return line
end

-- ###################################################

local function tcpPktsSentVsRcvd(hostname, host, label)
   local line
   local stats = interface.getHostInfo(host["ip"], host["vlan"])
   
   line = {
      meta = {
	 url_query = "host="..hostname,
	 label = label,
      },
      x = stats["tcp.packets.sent"],
      y = stats["tcp.packets.rcvd"],
      z = stats["tcp.bytes.sent"] + stats["tcp.bytes.rcvd"]
   }

   return line
end

-- ###################################################

local function tcpBytesSentVsRcvd(hostname, host, label)
   local line
   local stats = interface.getHostInfo(host["ip"], host["vlan"])

   line = {
      meta = {
	 url_query = "host="..hostname,
	 label = label,
      },
      x = stats["tcp.bytes.sent"],
      y = stats["tcp.bytes.rcvd"],
      z = stats["tcp.bytes.sent"] + stats["tcp.bytes.rcvd"]
   }
   
   return line
end

-- ###################################################

local function activeAlertFlows(hostname, host, label)
   local line

   if (host["active_alerted_flows"] > 0) then
      line = {
	 meta = {
	    url_query = "host="..hostname,
	    label = label,
	 },
	 x = host["active_flows.as_server"],
	 y = host["active_flows.as_client"],
	 z = host["active_alerted_flows"]
      }
   end

   return line
end

-- ###################################################

local function trafficRatio(hostname, host, label)
   local line

   line = {
      meta = {
	 url_query = "host="..hostname,
	 label = label,
      },
      x = host["bytes_ratio"],
      y = host["pkts_ratio"],
      z = host["bytes.sent"] + host["bytes.rcvd"]
   }
   
   return line
end

-- ###################################################

local function score(hostname, host, label)
   local line

   line = {
      meta = {
	 url_query = "host="..hostname,
	 label = label,
      },
      x = host["score.as_client"],
      y = host["score.as_server"],
      z = host["score.as_client"] + host["score.as_server"]
   }
   
   return line
end

-- ###################################################

local function blacklistedFlowsHosts(hostname, host, label)
   local line

   line = {
      meta = {
	 url_query = "host="..hostname,
	 label = label,
      },
      x = host.num_blacklisted_flows.as_client,
      y = host.num_blacklisted_flows.as_server,
      z = host.num_blacklisted_flows.as_client + host.num_blacklisted_flows.as_server
   }
   
   return line
end

-- ###################################################

local function processHost(hostname, host)
    local line
    local label = host.name

    check_condition = (host.localhost ~= nil) and (host.localhost == true)

    -- starts is defined inside the lua_utils module
    if ((label == nil) or (string.len(label) == 0) or starts(label, "@")) then
        label = hostname
    end

    line = nil

    line = formatHost(hostname, host, label)

    if (line ~= nil) then        
       if (line.z > max_radius) then
	  max_radius = line.z
       end

       if(check_condition) then
	  table.insert(first_host_table, line)
       else
	  table.insert(second_host_table, line)
       end
    end
end

-- ###################################################

-- Switch case used to know which function to format the hosts needs to be used
if (bubble_mode == HostsMapMode.ALL_FLOWS) then
   formatHost = allFlows
elseif (bubble_mode == HostsMapMode.UNREACHABLE_FLOWS) then
   formatHost = unreachableFlows    
elseif (bubble_mode == HostsMapMode.ALERTED_FLOWS) then
   formatHost = alertedFlows
elseif (bubble_mode == HostsMapMode.DNS_QUERIES) then
   formatHost = dnsQueries
elseif (bubble_mode == HostsMapMode.SYN_DISTRIBUTION) then
   formatHost = synDistribution
elseif (bubble_mode == HostsMapMode.SYN_VS_RST) then
   formatHost = synVsRst    
elseif (bubble_mode == HostsMapMode.SYN_VS_SYNACK) then
   formatHost = synVsSynack    
elseif (bubble_mode == HostsMapMode.TCP_PKTS_SENT_VS_RCVD) then
   formatHost = tcpPktsSentVsRcvd    
elseif (bubble_mode == HostsMapMode.TCP_BYTES_SENT_VS_RCVD) then
   formatHost = tcpBytesSentVsRcvd
elseif (bubble_mode == HostsMapMode.ACTIVE_ALERT_FLOWS) then
   formatHost = activeAlertFlows
elseif (bubble_mode == HostsMapMode.TRAFFIC_RATIO) then
   formatHost = trafficRatio    
elseif (bubble_mode == HostsMapMode.SCORE) then
   formatHost = score
elseif (bubble_mode == HostsMapMode.BLACKLISTED_FLOWS_HOSTS) then
   formatHost = blacklistedFlowsHosts
end

-- Callback cycle
if (show_remote) then
    callback_utils.foreachHost(ifname, processHost)
else
    callback_utils.foreachLocalHost(ifname, processHost)
end

-- Reduce the number of hosts to a reasonable value (< max_num)
local max_num = 999
first_host_table  = shrinkTable(first_host_table, max_num)
second_host_table = shrinkTable(second_host_table, max_num)

-- Normalize values
local ratio = max_radius / MAX_RADIUS_PX
for i,v in pairs(first_host_table) do 
    first_host_table[i].z = math.floor(MIN_RADIUS_PX + first_host_table[i].z / ratio) 
end

if (show_remote) then
    for i,v in pairs(second_host_table) do 
        second_host_table[i].z = math.floor(MIN_RADIUS_PX + second_host_table[i].z / ratio) 
    end
end

local base_url = ntop.getHttpPrefix() .. "/lua/host_details.lua"

-- Formatting Answer
rest_utils.answer(rc, {
   series = {
      {data = first_host_table, name = first_table_name, base_url = base_url},
      {data = second_host_table, name = second_table_name, base_url = base_url},
   },
   chart = {
      zoom = {
         autoScaleYaxis = true
      },
   },
   events = {
      dataPointSelection = "standard", 
   },
   grid = {
      padding = {
         left = 6
      },
   },
   colors = {"rgba(153, 102, 255, 0.45)", "rgba(255, 159, 64, 0.45)"},
   xaxis = {
      type = 'numeric',
      title = {
         text = MODES[bubble_mode + 1].x_label,
      },
      labels = {
         ntop_utils_formatter = MODES[bubble_mode + 1].x_formatter or 'fnone',
      }
   },
   yaxis = {
      type = 'numeric',
      forceNiceScale = true,
      title = {
         text = MODES[bubble_mode + 1].y_label,
         offsetX = 6
      },
      labels = {
         ntop_utils_formatter = MODES[bubble_mode + 1].y_formatter or 'fnone',           
      }
   },
   dataLabels = {
      enabled = false
   },
   tooltip = {
      custom = "format_label_from_xy",
   }
})
