#! /usr/bin/ruby1.8
# == Synopsis
#
# mauvesend: send alert(s) to a given alert station
#
# == Usage
#
# mauvesend [destination]
#   [--source | -o <source>] [--replace | -p] [--verbose | -v]
#   [--id <alert ID> [alert options] ... ]
#
# <destination>:
#   where the alert should go, can be one of:
#     SRV record from DNS (we add _mauvealert._udp to record name)
#     normal hostname (i.e. A record)
#     IP address:port number
#
#   if no destination is supplied, reads parameter from file 
#   /etc/mauvealert/mauvesend.destination (otherwise throws an error).
# 
# --source | -o <source>:
#   identify the source of the alert (defaults to hostname, but you might
#   want to name your monitoring systems more explicitly).
#
# --replace | -p:
#   Send an update replacing all other alerts for this source - any previous
#   alerts not specified in this update are assumed to be cleared.  If you
#   specify this option, you don't have to supply *any* alerts to raise or 
#   clear (in which case all alerts from that source will be cleared).
#
# --verbose | -v:
#   If you specify this option once, it will print the transmission ID
#   of the packet for debugging.  If you specify it twice, it will print
#   the entire data structure.
#
# You can specify any number of alerts in an update - every time you specify
# --id starts a new alert.
#
# --id | -i <alert ID>:
#   alert ID; unique specified for each alert raised.
#
# --summary | -s <summary>:
#   text for humans describing the nature of the alert, first 100 characters
#   are only ones guaranteed to make it to pagers, twitter, SMS etc.
#
# --detail | -d <detail>:
#   HTML fragment describing the alert in more detail, no limit on length.
#
# --subject | -u <subject>:
#   set the subject of the alert (i.e. the server/entity that this alert
#   concerns).
#
# --clear | -c <time>:
#   mark the alert to be cleared at the given time, or +N where N is a number
#   of seconds, or 'now'.  If not supplied, the alert is deemed to be raised 
#   until explicitly cleared.
#
# --raise | -r <time>:
#   mark the alert to be (re)raised at the given time.  If not supplied, the
#   alert will be raised immediately.
# 

require 'getoptlong'
require 'mauve/sender'
require 'mauve/proto'
require 'mauve/mauve_time'
require 'pp'

NOW = Mauve::MauveTime.now

def error(msg)
  STDERR.print "*** Error: #{msg}\n"
  STDERR.print "*** For help, type: #{$0} -h\n"
  exit 1
end

def parse_time_spec(spec = "now")
  now = NOW

  return now if spec == 'now'

  case spec[0]
    when ?+ then multiplier = 1
    when ?- then multiplier = -1
    else
      return Mauve::MauveTime.parse(spec)
  end
  multiplier *= case spec[-1]
    when ?m then 60
    when ?h then 3600
    when ?d then 86400
    else
      1
  end

  now + spec[1..-1].to_i * multiplier
end

update = Mauve::Proto::AlertUpdate.new
update.replace = false
update.alert = []
message = nil
verbose = 0
help    = false

opts = GetoptLong.new(
  ['-h', '--help',    GetoptLong::NO_ARGUMENT], 
  ['-o', '--source',  GetoptLong::OPTIONAL_ARGUMENT],
  ['-p', '--replace', GetoptLong::NO_ARGUMENT],
  ['-i', '--id',      GetoptLong::OPTIONAL_ARGUMENT],
  ['-s', '--summary', GetoptLong::OPTIONAL_ARGUMENT],
  ['-u', '--subject', GetoptLong::OPTIONAL_ARGUMENT],
  ['-c', '--clear',   GetoptLong::OPTIONAL_ARGUMENT],
  ['-r', '--raise',   GetoptLong::OPTIONAL_ARGUMENT],
  ['-d', '--detail',  GetoptLong::OPTIONAL_ARGUMENT],
  ['-v', '--verbose', GetoptLong::NO_ARGUMENT]
).each do |opt,arg|

  #
  # Can catch empty arguments better if we set the GetoptLong things to
  # "optional" rather than "required" and catch the empty arg here.
  error "#{opt} cannot be empty" if arg.empty? and not %w(-h -p -v -c -r).include?(opt)

  case opt
    when '-h'
      help = true
    when '-p'
      update.replace = true
    when '-i'
      message = Mauve::Proto::Alert.new
      message.id = arg
      update.alert << message
    when '-o'
      error "Can only specify one source" if update.source
      update.source = arg
    when '-v'
      verbose += 1
    else
      error "Must specify --id before message" unless message
      case opt
        when '-s' then message.summary = arg
        when '-u' then message.subject = arg
        when '-d' then message.detail = arg
        when '-c' then message.clear_time = parse_time_spec(arg).to_i
        when '-r' then message.raise_time = parse_time_spec(arg).to_i
        else
          error "Unknown option #{opt}"
      end
  end
end

# CAUTION! Kwality kode.
#
if help
  # Open the file, stripping the shebang line
  lines = File.open(__FILE__){|fh| fh.readlines}[2..-1]

  lines.each do |line|
  line.chomp!
    break if line.empty?
    puts line[2..-1].to_s
  end

  exit 0
end

error "No alerts specified" unless !update.alert.empty? || update.replace

update.transmission_id = rand(2**63)

begin
  Mauve::Sender.new(ARGV).send(update, verbose)
rescue Protobuf::NotInitializedError => bad
  error "Alert not initialized fully - you must supply an ID"
rescue ArgumentError => ae
  error ae.message
rescue StandardError => ae
  error ae.message
end