#!/usr/bin/env python
#
#   vote
#
#   Voting logic for EAS messaging.
#
#   Copyright (C) 2018 by Matt Roberts.
#   License: GNU GPL3 (www.gnu.org)
#
#   This is a pipeline utility, reading from standard intput, and writing to
#   standard output.  For any two matching lines read from standard intput,
#   in any group of two or three lines, one matching output line will be
#   written to standard output.
#
#   The standard format for EAS alerts is to send every message three times,
#   separated by one second.  This utility will read the output of the 'nwsrx'
#   utility, and attempt to find adjacent messages (in proximity and time)
#   that are equivalent. Duplicate messages will be suppressed, and one copy
#   is copied from stdin to stdout.
#
#   This also serves to suppress noise or corrupted data, since the only
#   lines written to the standard output are those who occur multiple times
#   in the data stream, and are separated by no more than one other message.
#
#   See 47 CFR 11 for details on the EAS, message content, and broadcast
#   transmission conventions.
#
#   Usage: vote [<translator command line> ...]
#
#   If called with no arguments, the 'vote' utility attempts to extract
#   unique messages from standard input, and copy them to standard output,
#   as described above.  If additional items appear on the command line,
#   they are treated as a command and arguments to pass to a second-level
#   translator, such as dsame.py.  When a unique line is discovered, the
#   second-level translator is run as a child process, and the line passed
#   into its standard input.  If any lines are produced by the child process,
#   they are passed to the standard output of 'vote'.
#
#

# modules we use
import sys, time, subprocess

#
#  Configuration Items
#

# the oldest message to compare
oldest = 60 # sec

# enable this to put a dotted line between each decoded message when a
#    second-level translator is used; ignored otherwise
separator = True

# enable this to put a timestamp above each decoded message when a
#    second-level translator is used; ignored otherwise
showdate = True


#
#  Global Variables
#

# the message history
messages = [ ]

# time history corresponding to 'messages'
times = [ ]

# the max queue size
max_msgs = 6

# the last message text
last_msg = ""


#
#  run_parser()
#
#  Run a parsing program and pipeline a message through it for conversion.
#
def run_parser(message):
	# if nothing to do, do nothing
	if not message:
		return [ "" ]
	message = message.strip()
	if not message:
		return [ "" ]

	# call the process using the balance of the command line
	args = sys.argv[1:]
	if not args:
		return [ message ]
	try:
		p = subprocess.Popen(
			args,
			shell = False,
			stdin=subprocess.PIPE,
			stdout=subprocess.PIPE,
			#stderr=open(os.devnull, 'w'),
			bufsize=256,
			close_fds=True)
		p.stdin.write("%s\n" % message.strip())
		p.stdin.flush()
		p.stdin.close()
		results = p.stdout.readlines()
		filtered = [ ]
		for line in results:
			line = line.strip()
			if line:
				filtered.append(line)
		return filtered
	except Exception, ex:
		sys.stderr.write("WARNING: vote could not run command '%s'... data not converted\n" % args[0])
		sys.stderr.write("WARNING: %s\n" % str(ex))
		return [ message ]


#
#  is_2nd_or_3rd(s)
#
#  Return true if the message supplied matches either of the last two messages received,
#  and within the allowed timeframe specified by 'oldest'.
#
def is_2nd_or_3rd(s):
	global messages
	global times
	global oldest

	for i in (1, 2):
		if len(messages) >= i:
			if (s == messages[len(messages) - i]) and (time.time() - times[len(times) - i] <= oldest):
				return True
	return False


#
#  main()
#
def main():
	global messages
	global last_msg
	global max_msgs
	global separator

	# number of records processed
	processed = 0

	while True:
		# read a line
		line = ''
		try:
			line = sys.stdin.readline()
		except KeyboardInterrupt:
			break
		# if EOF, exit
		if not line:
			break
		# trim all whitespace from the line ends (including newline)
		line = line.strip()

		# look for the message in previous lines; output if *new* match
		if messages and is_2nd_or_3rd(line) and line != last_msg:
			last_msg = line

			# if a translator program was specified, was the message through it
			if len(sys.argv) > 1:
				lines = run_parser(line)
				if lines:
					# print a separator line or just a newline
					if separator:
						sys.stdout.write('- ' * 38)
						if showdate:
							sys.stdout.write('\n')
							sys.stdout.write("Received %s %s:\n" % (
								time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
								time.tzname[time.localtime().tm_isdst])
							)
					sys.stdout.write('\n')

					# then output all the translated lines
					processed += 1
					for out in lines:
						if separator and showdate:
							sys.stdout.write("   ")
						sys.stdout.write("%s\n" % out)

			# otherwise, just print the raw message
			else:
				sys.stdout.write("%s\n" % line)

		# update the message history
		messages.append(line)
		times.append(time.time())
		while len(messages) > max_msgs:
			del(messages[0])
		while len(times) > max_msgs:
			del(times[0])

	if processed and len(sys.argv) > 1:
		if separator:
			sys.stdout.write('- ' * 38)
		sys.stdout.write('\n')

# entry point
if __name__ == '__main__':
	main()

# EOF
