var zlib   = require('zlib')
var dgram  = require('dgram')
var crypto = require('crypto')

// From https://github.com/Graylog2/graylog2-docs/wiki/GELF
// and https://github.com/Graylog2/gelf-php/blob/master/GELFMessage.php
var GELF_ID      = [0x1e, 0x0f]
  , GELF_KEYS    = ['version', 'host', 'short_message', 'full_message',
                    'timestamp', 'level', 'facility', 'line', 'file']
  , ILLEGAL_KEYS = ['_id']

function Gelfling(host, port, options) {
  this.host = host != null ? host : 'localhost'
  this.port = port != null ? port : 12201
  if (options == null) options = {}

  this.maxChunkSize = this.getMaxChunkSize(options.maxChunkSize)
  this.defaults = options.defaults || {}
  this.errHandler = options.errHandler || console.error
  this.keepAlive = options.keepAlive
}

Gelfling.prototype.send = function(data, callback) {
  if (callback == null) callback = function() {}
  if (Buffer.isBuffer(data)) data = [data]
  var udpClient, remaining, i, that = this

  if (!Array.isArray(data))
    return this.encode(this.convert(data), function(err, chunks) {
      if (err) return callback(err)
      that.send(chunks, callback)
    })

  if (!this.keepAlive || !this.udpClient) {
    udpClient = dgram.createSocket('udp4')
    udpClient.on('error', this.errHandler)
    if (this.keepAlive) this.udpClient = udpClient
  } else {
    udpClient = this.udpClient
  }
  remaining = data.length
  function checkDone(err) {
    if (err || --remaining === 0) {
      if (!that.keepAlive) udpClient.close()
      callback(err)
    }
  }
  for (i = 0; i < data.length; i++)
    udpClient.send(data[i], 0, data[i].length, this.port, this.host, checkDone)
}

Gelfling.prototype.close = function() {
  if (this.udpClient) this.udpClient.close()
}

Gelfling.prototype.encode = function(msg, callback) {
  if (callback == null) callback = function() {}
  var that = this
  zlib.gzip(new Buffer(JSON.stringify(msg)), function(err, compressed) {
    if (err) return callback(err)
    callback(null, that.split(compressed))
  })
}

Gelfling.prototype.split = function(data, chunkSize) {
  if (chunkSize == null) chunkSize = this.maxChunkSize
  if (data.length <= chunkSize) return [data]

  var msgId     = [].slice.call(crypto.randomBytes(8))
    , numChunks = Math.ceil(data.length / chunkSize)
    , chunks    = new Array(numChunks)
    , chunkIx, dataSlice, dataStart

  for (chunkIx = 0; chunkIx < numChunks; chunkIx++) {
    dataStart = chunkIx * chunkSize
    dataSlice = [].slice.call(data, dataStart, dataStart + chunkSize)
    chunks[chunkIx] = new Buffer(GELF_ID.concat(msgId, chunkIx, numChunks, dataSlice))
  }

  return chunks
}

Gelfling.prototype.convert = function(msg) {
  if (typeof msg !== 'object') msg = { short_message: msg }

  var gelfMsg  = {}
    , defaults = this.defaults
    , key, val

  for (key in defaults) {
    if (!defaults.hasOwnProperty(key)) continue
    val = defaults[key]
    gelfMsg[key] = typeof val === 'function' ? val(msg) : val
  }

  for (key in msg) {
    if (!msg.hasOwnProperty(key)) continue
    val = msg[key]
    if (GELF_KEYS.indexOf(key) < 0) key = '_' + key
    if (ILLEGAL_KEYS.indexOf(key) >= 0) key = '_' + key
    gelfMsg[key] = val
  }

  if (gelfMsg.version == null) gelfMsg.version = '1.0'
  if (gelfMsg.host == null) gelfMsg.host = require('os').hostname()
  if (gelfMsg.timestamp == null) gelfMsg.timestamp = +(new Date) / 1000
  if (gelfMsg.short_message == null) gelfMsg.short_message = JSON.stringify(msg)

  return gelfMsg
}

Gelfling.prototype.getMaxChunkSize = function(size) {
  if (size == null) size = 'wan'
  switch (size.toLowerCase()) {
    case 'wan': return 1420
    case 'lan': return 8154
    default:    return parseInt(size, 10)
  }
}

var gelfling = module.exports = function(host, port, options) {
  return new Gelfling(host, port, options)
}
gelfling.Gelfling = Gelfling

gelfling.EMERGENCY = 0
gelfling.ALERT     = 1
gelfling.CRITICAL  = 2
gelfling.ERROR     = 3
gelfling.WARNING   = 4
gelfling.NOTICE    = 5
gelfling.INFO      = 6
gelfling.DEBUG     = 7
