--[[
Copyright (C) 2013-2018 Draios Inc dba Sysdig.

This file is part of sysdig.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

--]]

-- Statsd client
--
-- For statsd protocol info: https://github.com/b/statsd_spec

local math = require "math"
local os = require "os"

math.randomseed(os.time())

local function send_to_socket(self, string)
  return sysdig.udp_send(string)
end

local function make_statsd_message(self, stat, delta, kind, sample_rate)
  -- Build prefix
  local prefix = ""

  if self.namespace ~= nil then prefix = self.namespace.."." end

  -- Escape the stat name
  stat = stat:gsub("[:|@]", "_")

  -- Append the sample rate
  local rate = ""

  if sample_rate ~= 1 then rate = "|@"..sample_rate end

  return prefix..stat..":"..delta.."|"..kind..rate
end

local function send(self, stat, delta, kind, sample_rate, neg)
  local packet_size = self.packet_size

  local msg
  local stat_type = type(stat)
  if stat_type == 'table' then sample_rate = delta end
  sample_rate = sample_rate or 1
  if not (sample_rate == 1 or math.random() <= sample_rate) then
    return
  end

  if stat_type == 'table' then
    local t, size = {}, 0
    for s, v in pairs(stat) do
      if kind == 'c' then
        if type(s) == 'number' then
          -- this is array of keys ( increment{'register', 'register_accept'})
          s, v = v, 1
        end
        v = neg and -v or v
      end
      msg = make_statsd_message(self, s, v, kind, sample_rate)
      size = size + #msg

      if t[1] and (size > packet_size) then
        local msg = table.concat(t, "\n")
        local ok, err = self:send_to_socket(msg)
        if not ok then return nil, err end
        t, size = {}, 0
      end

      t[#t + 1] = msg
    end
    msg = table.concat(t, "\n")
  else
    msg = make_statsd_message(self, stat, delta, kind, sample_rate)
  end

  return self:send_to_socket(msg)
end

-- Record an instantaneous measurement. It's different from a counter in that
-- the value is calculated by the client rather than the server.
local function gauge(self, stat, value, sample_rate)
  return self:send(stat, value, "g", sample_rate)
end

local function counter_(self, stat, value, sample_rate, ...)
  return self:send(stat, value, "c", sample_rate, ...)
end

-- A counter is a gauge whose value is calculated by the statsd server. The
-- client merely gives a delta value by which to change the gauge value.
local function counter(self, stat, value, sample_rate)
  return counter_(self, stat, value, sample_rate)
end

-- Increment a counter by `value`.
local function increment(self, stat, value, sample_rate)
  return counter_(self, stat, value or 1, sample_rate, false)
end

-- Decrement a counter by `value`.
local function decrement(self, stat, value, sample_rate)
  value = value or 1
  if type(stat) == 'string' then value = -value end
  return counter_(self, stat, value, sample_rate, true)
end

-- A timer is a measure of the number of milliseconds elapsed between a start
-- and end time, for example the time to complete rendering of a web page for
-- a user.
local function timer(self, stat, ms)
  return self:send(stat, ms, "ms")
end

-- A histogram is a measure of the distribution of timer values over time,
-- calculated by the statsd server. Not supported by all statsd implementations.
local function histogram(self, stat, value)
  return self:send(stat, value, "h")
end

-- A meter measures the rate of events over time, calculated by the Statsd
-- server. Not supported by all statsd implementations.
local function meter(self, stat, value)
  return self:send(stat, value, "m")
end

-- A set counts unique occurrences of events between flushes. Not supported by
-- all statsd implementations.
local function set(self, stat, value)
  return self:send(stat, value, "s")
end

return function(options)
  options = options or {}

  local host = options.host or "127.0.0.1"
  local port = options.port or 8125
  local namespace  = options.namespace or nil
  local packet_size = options.packet_size or 508 --  RFC791

  sysdig.udp_setpeername(host, port)

  return {
    namespace = namespace,
    udp = udp,
    packet_size = packet_size,
    gauge = gauge,
    counter = counter,
    increment = increment,
    decrement = decrement,
    timer = timer,
    histogram = histogram,
    meter = meter,
    send = send,
    send_to_socket = send_to_socket
  }
end
