#!/usr/bin/env python

import os
import sys
import glob
import time
import signal
import subprocess


def display (*args):
	sys.stdout.write(''.join(str(_) for _ in args))
	sys.stdout.flush()


class Alarm (Exception):
	pass


def alarm_handler (number, frame):  # pylint: disable=W0613
	raise Alarm()


def commands (exabgp, daemon, port):
	r = ""
	r += "command lines are:\n"
	r += 'export exabgp_debug_defensive=true\n'
	r += "> env exabgp_tcp_port=%d exabgp_tcp_once=true exabgp_debug.rotate=true exabgp_tcp_bind='' " % port + ' '.join(exabgp) + '\n'
	r += '> env exabgp_tcp_port=%d ' % port + ' '.join(daemon)
	r += '\n'
	return r


def make_short (groups):
	names = {}

	for group in groups:
		names[group.split('/')[-1].split('.')[0]] = group

	alphabet = [chr(_) for _ in range(ord('0'),ord('9')) + range(ord('A'),ord('Z')) + range(ord('a'),ord('z'))]
	# letter to name, name to letter
	l2n = dict(zip(alphabet,sorted(names.keys())))
	n2l = dict((r,l) for (l,r) in l2n.items())
	letters = sorted(l2n)

	return names, alphabet, l2n, n2l, letters


def check (exabgp, daemon, group, port, delay):
	directory = os.path.dirname(group)
	configurations = [os.path.join(directory,name) for name in open(group).read().strip().split()]
	sequence_file = group.replace('.group','.sequence')

	if not os.path.isfile(sequence_file):
		return False

	os.environ['exabgp_tcp_once'] = 'true'
	os.environ['exabgp_tcp_port'] = str(port)
	# os.environ['exabgp_debug_defensive'] = 'true'
	os.environ['exabgp_debug_rotate'] = 'true'
	os.environ['exabgp_reactor_speed'] = '0.1'

	daemon_command = [daemon, sequence_file]
	exabgp_command = [exabgp, '-d'] + configurations

	name = group.split('/')[-1].split('.')[0]

	try:
		if os.environ.get('DRY',os.environ.get('dry',False)) is not False:
			display(commands(exabgp_command,daemon_command,port))
			sys.exit(1)

		daemon = subprocess.Popen(daemon_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
		time.sleep(delay)
		exabgp = subprocess.Popen(exabgp_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

		exabgp_out,exabgp_err = exabgp.communicate()
		daemon_out,daemon_err = daemon.communicate()

		display(exabgp_out)
		display(exabgp_err)
		display(daemon_out)
		display(daemon_err)
	except Alarm:
		display(commands(exabgp_command,daemon_command,port))

		display('failed, killed', name, '\n')

		try:
			daemon.terminate()
		except OSError:
			pass
		try:
			exabgp.terminate()
		except OSError:
			pass
		time.sleep(1)
		try:
			daemon.kill()
		except OSError:
			pass
		try:
			exabgp.kill()
		except OSError:
			pass
	except Exception,exc:
		display(commands(exabgp_command,daemon_command,port))
		display(str(exc))


def dispatch (exabgp, daemon, groups, port):
	processes = {}
	out,err = '',''

	names, alphabet, l2n, n2l, letters = make_short(groups)

	class Color (object):
		RED   = '\033[0m' + '\033[91m'
		GREEN = '\033[1m' + '\033[92m'
		BLUE  = '\033[0m' + '\033[94m'
		END   = '\033[0m' + '\033[0m'

	display('\n')
	eol = False
	for letter in letters:
		display(letter,' ', l2n[letter], ' ' * (30 - len(l2n[letter])))
		if eol:
			display('\n')
		eol = not eol
	display('\n')

	if os.environ.get('LISTING',False):
		sys.exit(0)

	try:
		display('\n','starting','\n','--------','\n')

		for name in sorted(names.keys()):
			display(n2l[name],' ')
			port += 1
			processes[name] = subprocess.Popen([sys.argv[0],names[name],str(port)], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
		display('\n')
		display('\n','collected','\n','---------','\n')

		success = True
		color = dict(zip(l2n.keys(),[Color.BLUE]*100))
		while success and names.keys():
			for name in sorted(names.keys()):
				process = processes[name]
				returncode = process.poll()
				if returncode is not None:
					try:
						out,err = process.communicate()
						if 'failed' in out or 'expected' in out:
							color[n2l[name]] = Color.RED
							success = False
						else:
							color[n2l[name]] = Color.GREEN
							del names[name]
							out,err = '',''
							display('\r')
							display(*['%s%s%s' % (color[l],l if color[l] != Color.GREEN else '-', Color.END) + ' ' for l in letters])
							display(Color.END)
					except (IOError,OSError,ValueError):
						success = False

			time.sleep(1)
		display('\n')
	except Alarm:
		display('\n\n')
		display('failed, a test is taking more than the allocated time','\n\n')

	if names.keys():
		success = False
		display('alarming','\n','---------','\n')

		for name in names.keys():
			process = processes.get(name,None)
			if process:
				try:
					process.send_signal(signal.SIGALRM)
					display(name,' ')
				except OSError:
					pass
		display('\n')

		display('\n','error','\n','----------','\n')
		if out or err:
			display(out)
			display(err)
		else:
			for name in names.keys():
				process = processes.get(name,None)
				if process:
					try:
						out,err = process.communicate()
					except ValueError:
						pass
					display(out)
					display(err)
					display('\n','\n')
					try:
						process.terminate()
					except OSError:
						pass

	return success


if __name__ == '__main__':
	if len(sys.argv) > 2:
		number = sys.argv.pop(2)
		if not number.isdigit():
			sys.exit('invalid port value %s' % number)
		port = int(number)
		print 'using port', port
	else:
		max_test = 100
		port = 1024 + hash(sys.version) % (pow(2,16)-1024-max_test)

	uid = os.getuid()
	gid = os.getgid()

	if uid and gid and int(port) <= 1024:
		sys.exit('you need to have root privileges to bind to port 79')

	location = os.path.abspath(os.path.join(os.path.realpath(__file__),'..','..','..'))

	exabgp = os.path.join(location,'sbin','exabgp')
	if not os.path.isfile(exabgp):
		sys.exit("could not find exabgp")

	conf = os.path.join(location,'qa','conf')
	if not os.path.isdir(conf):
		sys.exit('could not find conf folder')

	daemon = os.path.join(location,'qa','sbin','bgp')
	if not os.path.isfile(daemon):
		sys.exit('could not find the sequence daemon')

	timeout = int(os.environ.get('TIMEOUT',os.environ.get('timeout',10*60)))  # seconds
	groups = sorted(glob.glob(os.path.join(location,'qa','conf','*.group')))[::-1]

	client = os.environ.get('CLIENT',os.environ.get('client','')).upper()
	if client:
		names, alphabet, l2n, n2l, letters = make_short(groups)
		command = "env exabgp_debug_configuration=true exabgp_tcp_port=%d exabgp_tcp_once=true exabgp_debug_rotate=true exabgp_tcp_bind='' %s -d -p %s.conf" % (port,exabgp,names[l2n[client]][:-6])
		print '>', command
		if os.system(command):
			sys.exit(1)
		sys.exit(0)

	server = os.environ.get('SERVER',os.environ.get('server','')).upper()
	if server:
		names, alphabet, l2n, n2l, letters = make_short(groups)
		command = "env exabgp_tcp_port=%d %s %s.sequence" % (port,daemon,names[l2n[server]][:-6])
		print '>', command
		if os.system(command):
			sys.exit(1)
		sys.exit(0)

	if len(sys.argv) >= 2:
		group = sys.argv[1]
		if not group.endswith('group'):
			sys.exit('wrong name as parameter, it must be the group file ending in .group (%s)' % group)
		success = check(exabgp,daemon,group,port,max(4,timeout/20))
	else:
		signal.signal(signal.SIGALRM, alarm_handler)
		signal.alarm(timeout)

		success = dispatch(exabgp,daemon,groups,port)

	sys.exit(0 if success else 1)
