#! /usr/bin/python

import sys
import os
from time import sleep, time, strftime
from skyd_func import skyd_status, skyd_claim, skyd_release, skyd_read_conf, skyd_write_conf
from tiny import hide_env, dict_entry, which, hide_env, start, is_there, say, run_cmd
from signal import SIGUSR1

grunt = os.environ['HOSTNAME'].split('.')[0]
prog  = 'smeidb_skyd'

def skyd_local_orbits(min_orbit,max_orbit):

	# zorro : dc1
	# zelda : dc2
	# sun   : dc3
	# ziggy : dc4
	# sid   : dc5
	# seth  : dc6
	# sun   : dc7

	if '_' in min_orbit or '_' in max_orbit:
		return []

	local_orbits = dict ( [	\
		 ('zorro', [ [    1, 5627] ] ),	\
		 ('zelda', [ [ 5627,10460] ] ),	\
		 ('sun ' , [ [10460,15180] ] ),	\
		 ('ziggy', [ [15180,20269] ] ),	\
		 ('sid'  , [ [20269,24847] ] ), \
		 ('seth' , [ [24847,29454] ] ), \
		 ('sun'  , [ [29454,35000] ] ) ] )

	if not local_orbits.has_key(grunt):
		return []

	min_orb = int(min_orbit)
	max_orb = int(max_orbit)

	orbits = local_orbits[grunt]

	if min_orb != 0:
		i = 0
		while i < len(orbits):
			if min_orb > orbits[i][1]:
				orbits[i:i+1] = []
			else:
				if min_orb > orbits[i][0]:
					orbits[i][0] = min_orb
				i += 1

	if max_orb != 0:
		i = 0
		while i < len(orbits):
			if max_orb < orbits[i][0]:
				orbits[i:i+1] = []
			else:
				if max_orb < orbits[i][1]:
					orbits[i][1] = max_orb
				i += 1

	for i in range(len(orbits)):
		orbits[i][0] = '%d'%orbits[i][0]
		orbits[i][1] = '%d'%orbits[i][1]

	return orbits

def skyd_getorbit(orbits,orbnr):

	for orbit in orbits:		# Loop over all orbit entries
		tmp = orbit.split()
		if len(tmp) > 0:		# Not an empty line
			if tmp[0] == orbnr:	# Orbit entry matches orbnr
				break
	else:
		orbit = ''

	return orbit

def skyd_cat(filepath,checkver,prog_version,overwrite,ignore_cat,task,orbnr,min_orbit,max_orbit,status):
	"""
#+
# NAME:
#	skyd_cat
# PURPOSE:
#	Manipulates the orbit catalogue
# CALLING SEQUENCE:
#	status = skyd_cat(filepath,checkver,prog_version,overwrite,ignore_cat,task,orbnr,min_orbit,max_orbit,status)
# INPUTS:
#	filepath	string		file name of user catalogue
#	checkver	integer		0: do not check version number
#					1: check version number
#	prog_version string		smei_skyd version number
#	overwrite	integer		0: do not overwrite existing skymap
#					1: overwrite existing skymap
#	task		string		task to be performed
#					one of 'set_busy','set_make','set_done','set_skip'
#					'set_busy': select an orbit for indexing
#	orb_nr		string		orbit number (as a string!, e.g. '2012'
#					used for task 'set_make', 'set_done' and 'set_skip'
#	min_orbit	string		lowest orbit number to be processed
#	max_orbit	string		highest orbit number to be processed
#	status		dictionary	status dictionary
# OUTPUTS:
#	status		dictionary	status dictionary
#					on success:
#					status['number'] and status['message'] are not modified
#					(should be 0 and blank string, respectively)
#					key 'orbit' contains the full record from the catalogue
#					for the relevant orbit.
#					on failure:
#					status['number' ] is set to 1
#					status['message'] contains error message
#					Possible reasons:
#					- skyd_claim failed to claim the catalogue
#					- invalid task
#					- no orbit left (task 'set_make')
#					- input orbit orb_nr is not marked "busy"
#					- skyd_release failed to release the catalogue
# CALLS:
#	skyd_claim, skyd_release, skyd_status
# MODIFICATION HISTORY:
#	DEC-2005, Paul Hick (UCSD/CASS)
#	DEC-2005, Paul Hick (UCSD/CASS)
#		Modified to skip empty lines and comments (beginning
#		with # character).
#	MAY-2007, Paul Hick (UCSD/CASS; pphick@ucsd.edu)
#		When looking for a "busy" orbit allow also "done" and "make"
#		to pass. If this happens the orbit is set to "make" and hence
#		will be redone.
#-
	"""
	hidepath = hide_env(filepath)

	# The catalogue file is claimed by enabling read protection
	# until we are done with it. This should avoid that
	# another run accesses the catalogue and decides to process
	# the same orbit.

	# The drawback is that we have to remember to switch read
	# protection off again before returning if something goes wrong.

	if ignore_cat:
		status = skyd_claim(False,status,filepath)
	else:
		status = skyd_claim(True,status,filepath)

	if status['number'] != 0:
		return status

	# Pop key 'contents' added by skyd_claim

	orbits = status.pop('contents')

	if task == 'set_busy':			# Select orbit for indexing

		if orbnr != '0':

			# This is only for interactive purposes. The daemon always
			# determines the orbit itself.

			orbit = skyd_getorbit(orbits,orbnr)

			if orbit == '':		# Orbit not found
				status = skyd_status(status,1,'orbit %s not present, %s'%(orbnr,hidepath))
			elif 'done' in orbit:	# Orbit found but is already done

				if overwrite:
					count = 0

				elif checkver:
					tmp = orbit.split()

					if len(tmp) < 6:# No version present
						count = 0

					elif float(prog_version) > float(tmp[5]):
						count = 0
					else:
						status = skyd_status(status,1,'orbit %s already done by same/higher version, %s'%(orbnr,hidepath))

				else:
					status = skyd_status(status,1,'orbit %s already done, %s'%(orbnr,hidepath))
			else:				# Orbit found
				count = 0
			
		else:
			# This is where the orbit to be processed is determined
			# from the catalogue. Look for an orbit marked as 'make'.

			for orbit in orbits:

				if len(orbit) == 0:	# Empty line
					continue
				if orbit.isspace():	# Only whitespace
					continue
				if orbit.find('#') == 0:
					continue	# Comment line

				# The int's are really necessary!

				tmp = orbit.split()

				if min_orbit != '0' and int(tmp[0]) < int(min_orbit):
					continue	# Orbit lower than min_orbit

				if max_orbit != '0' and int(tmp[0]) > int(max_orbit):
					continue	# Orbit higher than max_orbit

				if 'make' in orbit:
					if len(tmp) > 4:# Counter present: orbit has been tried before
						count = int(tmp[4])
					else:		# No counter present
						count = 0
					break

				if 'done' in orbit:
					#if overwrite:
					#	count = 0
					#	break

					if checkver:
						if len(tmp) > 5:# Version present
							if float(prog_version) > float(tmp[5]):
								count = 0
								break
						else:		# No version present
							count = 0
							break

			else:
				status = skyd_status(status,1,	\
					'no orbit left in [%s,%s], %s'%(min_orbit,max_orbit,hidepath))

		if status['number'] == 0:		# Succes: orbit selected

			# Found orbit marked 'make'. Change status to 'busy'.
			# Put the 'make' counter back, and add the current time.

			pos = orbits.index(orbit)	# Record number of orbit in catalogue

			n = max([orbit.find('make'),orbit.find('busy'),orbit.find('skip'),orbit.find('done')])
			orbit = orbit[0:n]+'busy %d %s %s'%(count,strftime('%Y_%j_%H%M%S'),grunt)

	elif task == 'set_make':			# Mark orbit orb_nr from "busy" to "make"
							# (done if the indexing of orb_nr failed)
		orbit = skyd_getorbit(orbits,orbnr)

		# The orbit should always be present, and should always be marked "busy",
		# but do some safety checks anyway.

		if orbit == '':				# Orbit not found
			status = skyd_status(status,1,'orbit %s not present, %s'%(orbnr,hidepath))
		elif 'busy' not in orbit:		# Orbit found, but not 'busy"
			state = orbit.split()[3]
			status = skyd_status(status,0,'orbit %s marked "%s" instead of "busy", %s'%(orbnr,state,hidepath))
		else:
			state = 'busy'

		if status['number'] == 0:	# Success: orb_nr is marked "busy"

			pos = orbits.index(orbit)# Record number of orb_nr in catalogue

			count = orbit.split()[4]
			if len(count) == 1:
				count = int(count)
			else:
				count = 0
			count += 1		# Increase "make" counter by one

			max_try = 3		# Only try to index orbit max_try times
			if count > max_try:	# max_try attempts done: mark orbit as "pass"
				orbit = orbit[0:orbit.find(state)]+'pass '+strftime('%Y_%j_%H%M%S')
			else:			# Mark orbit as "make" (will try again later)
				orbit = orbit[0:orbit.find(state)]+'make %d'%count

	elif task == 'set_done':		# Mark orbit orb_nr from "busy" to "done"
						# (done if indexing was successful)
		orbit = skyd_getorbit(orbits,orbnr)

		# The orbit should always be present, and should always be marked "busy",
		# but do some safety checks anyway. Sometimes status "done" or "make"
		# is encountered. Not sure why. If this happens the status is set back
		# to "make" and the orbit is redone later.

		if orbit == '':			# Orbit not found
			status = skyd_status(status,1,'orbit %s not present, %s'%(orbnr,hidepath))
		elif 'busy' not in orbit:	# Orbit found, but not 'busy"
			state = orbit.split()[3]
			if ignore_cat:
				orbit_ignore = orbit[0:orbit.find(state)]+'busy 0'
				state = 'busy'
			elif state == 'done':	# Status['number']=0: state is "done" already
				status = skyd_status(status,0,'orbit %s marked "%s" instead of "busy", %s'%(orbnr,state,hidepath))
			elif state == 'make':
				status = skyd_status(status,0,'orbit %s marked "%s" instead of "busy", %s'%(orbnr,state,hidepath))
			else:			# State is "busy", as expected
				status = skyd_status(status,1,'orbit %s marked "%s" instead of "busy", %s'%(orbnr,state,hidepath))
		else:
			state = 'busy'

		if status['number'] == 0:	# Success: orb_nr is marked "busy", "done" or "make"

			pos = orbits.index(orbit)# Record number of orb_nr in catalogue
			if ignore_cat:
				orbit = orbit_ignore

			if state == 'busy':	# State is "busy", as expected
				count = int(orbit.split()[4])
				count += 1	# Increase "make" counter by one
						# Mark orbit as "done"
				orbit = orbit[0:orbit.find(state)]+'done '+strftime('%Y_%j_%H%M%S')+' '+prog_version

			else:			# State is "done" or "make". Mark as "make" with count=0
				count = 0
				orbit = orbit[0:orbit.find(state)]+'make %d'%count

	elif task == 'set_skip':		# Mark orbit orb_nr from "busy" to "done"
						# (if no frames available for skymap)
		orbit = skyd_getorbit(orbits,orbnr)

		# The orbit should always be present, and should always be marked "busy",
		# but do some safety checks anyway.

		if orbit == '':			# Orbit not found
			status = skyd_status(status,1,'orbit %s not present, %s'%(orbnr,hidepath))
		elif 'busy' not in orbit:	# Orbit found, but not 'busy"
			state = orbit.split()[3]
			status = skyd_status(status,0,'orbit %s marked "%s" instead of "busy", %s'%(orbnr,state,hidepath))
		else:
			state = 'busy'

		if status['number'] == 0:	# Success: orb_nr is marked "busy"

			pos = orbits.index(orbit)# Record number of orb_nr in catalogue
						# Mark orbit as "skip"
			orbit = orbit[0:orbit.find(state)]+'skip '+strftime('%Y_%j_%H%M%S')+' '+prog_version

	else:					# Invalid task

		status = skyd_status(status,1,'invalid task, '+task)

	if status['number'] == 0:		# Success

		orbits[pos] = orbit		# Update status of orbit
		status['contents'] = orbits	# Attach content to status dictionary
						# Update and release the catalogue
						# This pops keys 'contents'
		if not ignore_cat:
			status = skyd_release(status,filepath)
		if status['number'] == 0:	# Add key 'orbit' (whole record from catalogue)
			status['orbit'] = orbit

	else:					# Failure, explicitly 'release' the catalogue

		if not ignore_cat:
			status = skyd_release(status,filepath)

	return status

def skyd_orbit(status):
	"""
#+
# NAME:
#	skyd_orbit
# PURPOSE:
#	Set up call to the indexing program
# CALLING SEQUENCE:
#	status = skyd_orbit(status)
# CALLS:
#	skyd_status, skyd_cat, dict_entry, say
# EXAMPLE:
#	To call skyd_orbit.py directly from command line use a call like this:
#		skyd_orbit.py -orbit=25182 -camera=1 -mode=2 -source=SMEIDB?
#			-avoidsun -avoidmoon
#			-destination=$SMEISKY0/sky/c1 -overwrite -alltheway
#			-catalogue=$SKYD/list/skyd_c1m2.txt -level=3
# MODIFICATION HISTORY:
#	DEC-2005, Paul Hick (UCSD/CASS)
#	DEC-2007, Paul Hick (UCSD/CASS)
#		Fixed bug in main section: added check for label
#		cur_label after skyd_orbit finishes to make sure
#		it still is present in the conf file.
#	SEP-2008, Paul Hick (UCSD/CASS)
#		Added argument sdark=<-1,3,10>
#	JAN-2013, Paul Hick (UCSD/CASS; pphick@ucsd.edu)
#		Added /fix_centroid to smei_star_remove call
#-
	"""
	csay = 'skyd_orbit'
	tub  = os.environ['TUB']

	args = status.pop('args')

	camera	  = dict_entry( args, 'camera'		,  1 )
	mode	  = dict_entry( args, 'mode'		, -1 )
	keepglare = dict_entry( args, 'keepglare'	,  0 )
	checkver  = dict_entry( args, 'checkversion',  0 )
	overwrite = dict_entry( args, 'overwrite'	,  0 )
	avoidsun  = dict_entry( args, 'avoidsun'	,  0 )
	avoidmoon = dict_entry( args, 'avoidmoon'	,  0 )
	nped_min  = dict_entry( args, 'nped_min'	,  0 )
	ndark_min = dict_entry( args, 'ndark_min'	,  0 )
	sdark	  = dict_entry( args, 'sdark'		, -1 )
	level	  = dict_entry( args, 'level'		, 11 )
	orbit	  = dict_entry( args, 'cur_orbit'	,'0' )
	min_orbit = dict_entry( args, 'min_orbit'	,'0' )
	max_orbit = dict_entry( args, 'max_orbit'	,'0' )
	catalogue = dict_entry( args, 'catalogue'	, '' )
	source	  = dict_entry( args, 'source'		, 'SMEIDC?' )
	dest	  = dict_entry( args, 'destination'	, tub )
	alltheway = dict_entry( args, 'alltheway'	,  0 )
	keepbkgnd = dict_entry( args, 'keepbkgnd'	,  0 )
	ignore_cat= dict_entry( args, 'ignore_cat'	,  0 )

	camera	  = int(camera	 )
	mode	  = int(mode  	 )
	keepglare = int(keepglare)
	checkver  = int(checkver )
	overwrite = int(overwrite)
	avoidsun  = int(avoidsun )
	avoidmoon = int(avoidmoon)
	nped_min  = int(nped_min )
	ndark_min = int(ndark_min)
	sdark	  = int(sdark	 )
	level	  = int(level	 )
	alltheway = int(alltheway)
	keepbkgnd = int(keepbkgnd)

	# Sanity checks, return non-zero status if something is wrong
	# (a non-zero status will kill the daemon if it is running)

	if which(prog) == '':
		return skyd_status(status,1,'executable not found, '+prog)

	if camera < 1 or camera > 3:
		return skyd_status(status,1,'camera %d is invalid (use 1,2 or 3)'%camera)

	if mode == -1:
		if camera == 1: mode = 2
		if camera == 2: mode = 2
		if camera == 3: mode = 1

	if mode < 0 or mode > 2:
		return skyd_status(status,1,'mode %d is invalid (use 0,1 or 2)'%mode)

	if sdark != -1 and sdark != 3 and sdark != 10:
		return skyd_status(status,1,'sdark %d is invalid (use -1,3,10)'%sdark)

	if source != 'SMEIDB?':
		source = 'SMEIDC?'

	# Check for presence of ":" in dest. If present all skymaps
	# are created locally in $TUB and are moved to the remote
	# destination on completion.

	make_locally = ':' in dest
	if make_locally:
		host = dest.split(':')[0]

	dest = os.path.expandvars(dest)

	if not make_locally and not os.path.isdir(dest):
		return skyd_status(status,1,'destination directory does not exist, '+hide_env(dest))

	if catalogue == '':
		return skyd_status(status,1,'no user catalogue specified, '+hide_env(catalogue))

	catalogue = os.path.expandvars(catalogue)

	if not os.path.isfile(catalogue):
		return skyd_status(status,1,'user catalogue does not exist, '+hide_env(catalogue))

	prog_version = run_cmd(prog+' -dumpversion',0).split()

	# This typically happens when too many smeidb_skyd copies are run, and
	# a the above smeidb_skyd -dumpversion is killed because there is not
	# enough memory.

	# Note that the next two lines will report this as an error back to the
	# daemon, which will then make another attempt to start smeidb_skyd, which
	# is killed again, etc, etc. So there is the potential for overloading the
	# the daemon with error messages here.

	# It may be better to comment the next two lines and let the
	#	prog_version = prog_version[0]
	# kill this script. No signal goes back to the daemon, and effectively the
	# number of smeidb_skyd runs is reduced by one. This is what is needed
	# even though it is accomplished in an ugly way.

	# Maybe the error message can be used by the daemon to do the same
	# more elegantly.

	if len(prog_version) == 0:
		return skyd_status(status,1,'could not determine '+prog+' version')

	prog_version = prog_version[0]

	# All seems OK.
	# If reading from SMEIDC? try to pick up a local orbit

	if source == 'SMEIDC?':
		local_orbits = skyd_local_orbits(min_orbit,max_orbit)
		status = skyd_status(status,0,'')
		for orb in local_orbits:
			status = skyd_cat(catalogue,checkver,prog_version,overwrite,ignore_cat,'set_busy',orbit,orb[0],orb[1],status)
			if status['number'] == 0:
				say(csay,'I','local',status['orbit'])
				break
			status = skyd_status(status,0,'')
		else:
			status = skyd_cat(catalogue,checkver,prog_version,overwrite,ignore_cat,'set_busy',orbit,min_orbit,max_orbit,status)

	else:
		status = skyd_cat(catalogue,checkver,prog_version,overwrite,ignore_cat,'set_busy',orbit,min_orbit,max_orbit,status)

	if status['number'] != 0:
		return status

	orbit = status.pop('orbit')

	# Set up the call to the smeidb_skyd program

	# orbit[0]		orbit nr (integer) or start time
	# orbit[1]		orbit[0]+1 or stop time
	#					if orbit[1] is not equal to orbit[0]+1 then smei_skyd
	#					is run with the -onesky keyword
	# orbit[2]		time used in construction of name for skymap
	#					used to confirm that the skymap was indeed created
	#					by the indexing program
	# orbit[3]		status

	orbit = orbit.split()

	try:
		orbit0 = int(orbit[0])
		orbit1 = int(orbit[1])
		onesky = orbit1+1 == orbit2
	except:
		onesky = False

	cmd = [prog,'-start='+orbit[0]]
	if onesky:
		cmd.extend(['-stop='+orbit[1],'-onesky'])
	cmd.extend(['-camera=%d'%camera,'-mode=%d'%mode,'-source='+source])
	cmd.append('-destination='+[dest,tub][make_locally])
	cmd.append('-silent=3')

	if keepglare:
		cmd.append('-keepglare')
	if checkver:
		cmd.append('-checkversion')
	if overwrite:
		cmd.append('-overwrite')
	if avoidsun:
		cmd.append('-avoidsun')
	if avoidmoon:
		cmd.append('-avoidmoon')
	if nped_min > 0 and ndark_min > 0:
		cmd.extend(['-nped_min=%d'%nped_min,'-ndark_min=%d'%ndark_min])
	if level < 11:
		cmd.append('-level=%d'%level)
	if sdark != -1:
		cmd.append('-sdark=%d'%sdark)

	say(csay,'I','cmd','\n'+(' '.join(cmd)))

	# If something goes wrong, the 'busy' status needs to be turned back
	# to 'make' with the 'make' counter increased by one.
	# 'Wrong' means either an exception occurred or smeidb_skyd returned
	# a zero error status (???).

	camstr  = 'c%d'%camera
	modestr = ['','m%d'%mode][mode  ==  0]
	dirstr  = camstr+['s',''][sdark == -1]+modestr

	sky_file = os.path.join([dest,tub][make_locally],camstr+'sky_'+orbit[2]+'.fts.gz')
	new_skymap_made = False

	try:

		rtn = os.spawnvp(os.P_WAIT, prog, cmd)
		#print csay+'return status '+prog+' is',rtn

		if rtn == 1:			# New file written
			if os.path.isfile(sky_file):
				rtn = 0		# New orbital skymap exists
				new_skymap_made = True
			else:
				rtn = 2		# Not supposed to happen

		elif rtn == 3:			# No new file created
			if os.path.isfile(sky_file):
				rtn = 0		# Orbital skymap exists
			else:
				rtn = 1		# No skymap; probably no frames

		else:
			rtn = 2			# smeidb_skyd returned error status

	except:
		rtn = 3				# An exception occurred

	if new_skymap_made and make_locally:
		run_cmd('scp '+sky_file+' '+dest+'; rm -v '+sky_file, True)
		sky_file = os.path.join(dest,os.path.split(sky_file)[1])

	if new_skymap_made and alltheway:

		sky0 = os.environ['SMEISKY0']
		if make_locally:
			sky0 = host+':'+sky0

		# If destination for skymaps is $SMEISKY0 then write equ maps also there
		# Otherwise write all files to the same destination directory.

		if dest == os.path.join(sky0,'sky',dirstr):
			star_dir  = os.path.join(sky0,'equ',dirstr)
			star_dest = '[\\"'+star_dir+'\\",\\"'+os.path.join(sky0,'pnt',dirstr)+'\\"]'
		else:
			star_dir  = dest
			star_dest = '\\"'+star_dir+'\\"'

		# Subtract stars

		if ':' in sky_file:
			sky_file = ':'.join(sky_file.split(':')[1:])
		cmd = 'smei_star_remove,\\"'+sky_file+'\\",'				 + \
			'/cleanedge,/use_weights,/auto_wing,/force,silent=2,/fix_centroid,'+ \
			'destination='+star_dest

		if keepbkgnd:
			cmd += ',/keepbkgnd'

		print cmd

		try:
			err = os.spawnlp(os.P_WAIT,'bash','bash','-c', 'IDL_DEV=Z; export IDL_DEV; echo "'+cmd+'" | idl -quiet')
		except:
			say(csay,'I','idl','exception during star subtraction')

		if not keepbkgnd:

			# If destination for skymaps is $SMEISKY0 then write ecl maps also there
			# Otherwise write all files to the same destination directory.

			if dest == os.path.join(sky0,'sky',dirstr):
				zld_dest = '\\"'+os.path.join(sky0,'ecl',dirstr)+'\\"'
			else:
				zld_dest = '\\"'+dest+'\\"'

			# Remove zodiacal light

			star_file = os.path.join(star_dir,camstr+'equ_'+orbit[2]+'.fts.gz')
			if ':' in star_file:
				star_file = ':'.join(star_file.split(':')[1:])

			cmd = 'smei_zld_remove,\\"'+star_file+'\\",'	+ \
				'/force,/silent,'						+ \
				'destination='+zld_dest

			print cmd

			try:
				err = os.spawnlp(os.P_WAIT,'bash','bash','-c', 'IDL_DEV=Z; export IDL_DEV; echo "'+cmd+'" | idl -quiet')
			except:
				say(csay,'I','idl','exception during zodiacal light model removal')

	# rtn = 0: smeidb_skyd finished and a skymap was found
	#			mark orbit as 'done'
	# rtn = 1: smeidb_skyd finished but there is no skymap (probably no frames)
	#			mark orbit as 'skip'
	# rtn = 2: smeidb_skyd returned an error
	#			mark orbit as 'make' (and try again later)
	# rtn = 3: an exception occurred
	#			mark orbit as 'make' (and try again later)
	# (note that rtn=2,3 means that we may have to set the _done flag for the
	# relevant group in the conf file back to zero).

	state = ['done','skip','make','make'][rtn]
	status = skyd_cat(catalogue,checkver,prog_version,overwrite,ignore_cat,'set_'+state,orbit[0],min_orbit,max_orbit,status)

	if status['number'] == 0:

		# We pick up the state from status['orbit'] value instead of using the
		# value of 'state' directly to make sure we don't miss orbits set to 'pass'

		tmp = status['orbit'].split()
		tmp = 'orbit '+tmp[0]+' marked "'+tmp[3]+'"'

		if rtn == 1:
			tmp += ', no '+hide_env(sky_file)
		elif rtn == 2:
			tmp += ', '+prog+' error'
		elif rtn == 3:
			tmp += ', exception'

		if status['message'] != '':
			tmp += ', '+status['message']

		status = skyd_status(status,0,tmp)

	return status

if __name__ == '__main__':

	status = dict ( [ ('number', 0), ('message', '') ])

	boss_pid = start( '-pid=', sys.argv )

	# If started with -pid then presumably skyd_wait is in control
	# (either started manually or by the skyd daemon)

	if boss_pid != '':				# skyd_wait is in control

		boss_pid = int(boss_pid)
		boss   = start( '-boss='  ,sys.argv )
		report = start( '-report=',sys.argv )

		grunt_pid = os.getpid()

		# boss_pid was passed as cmd line arg primarily to allow skyd_orbit to
		# send back a signal to skyd_wait before cffile is read.
		# Write <report>_started file and send SIGUSR1 signal back to skyd_wait
		# to indicate that run has started

		wmark  = os.path.split(report)[1]
		result = 'runs'

		open(report+'_'+result,'w').write('%s pid is %d\n'%(grunt,grunt_pid))

		say('skyd_orbit','I',wmark,'%s, "%s" pid %d -> "%s" pid %d'%(result,grunt,grunt_pid,boss,boss_pid))

		if boss == grunt:		# skyd_wait is running on same machine

			os.kill(boss_pid,SIGUSR1)

		else:					# skyd_wait is running on different machine

			sleep(5)			# Safety belt
			result = (os.popen('ssh '+boss+' "kill -%d %d"'%(SIGUSR1,boss_pid))).read()
			say('skyd_orbit','I',wmark,result)

		# Now that the signal has been send, read the configuration file

		cffile = start( '-cffile=',sys.argv )

		# Read the configuration file (there should be one for each computer
		# that we plan to run the daemon on).

		status = skyd_read_conf(cffile,False,status)

		if status['number'] == 0:

			conf = status.pop('conf')

			# Count number of groups in conf file

			max_group = 0
			for key in conf.keys():
				if key.find('group_') == 0:
					max_group += 1

			# Find the next group for which to process an orbit
			# The previos orbit was from conf['cur_group'], so start checking
			# one group up from this group in a cyclic fashion.
			# Quit if there are no more orbits to process

			cur_group = int(conf['cur_group'])
			fnd_group = 0

			while fnd_group < max_group:
				cur_group = (cur_group+1)%max_group
				cur_label = 'group_%d'%cur_group
				if conf[cur_label].has_key('done'):
					if conf[cur_label]['done'] == '0':
						break
					fnd_group += 1
				else:
					break
			else:
				status = skyd_status(status,1,'all orbits processed')

			if status['number'] == 0:

				# cur_group is the next group selected.
				# cur_label is its name, and conf[cur_label] are the conf file
				# entries associated with this group. This is fed to skyd_orbit
				# by adding it as the 'args' entry to the status dictionary.

				lapsed_time = time()

				status['args'] = conf[cur_label]

				#============
				# This does all the dirty work and takes an hour or two to complete.

				status = skyd_orbit(status)

				lapsed_time = (time()-lapsed_time)/60.0
				say('skyd_orbit','I','time lapsed','is %.3f minutes'%lapsed_time)

				#============

				# status['number'] != 0 will result in a kill signal to the
				# skyd.py daemon. If the error message was that there are no
				# more orbits to process in the cur_group section we don't want
				# that to happen (there may still be orbits to be done in one
				# of the other sections).

				done = 'no orbit left in' in status['message']
				if done:
					status['number'] = 0

				if status['number'] == 0:

					# Update the conf file for group cur_group

					status = skyd_read_conf(cffile,True,status)
					if status['number'] == 0:
						conf = status.pop('conf')

						# Check whether cur_label still exists
						# (the conf file may have been edited).
						# If the group cur_group doesn't exist anymore
						# set cur_group to zero.

						if conf.has_key(cur_label):	# cur_label exists?
							conf['cur_group'] = '%d'%cur_group# Update cur_group
							if done:		# No more orbits then ..
								conf[cur_label]['done'] = '1'# .. mark group as done
							elif conf[cur_label]['done'] == '1':# Group done, but ..
								if 'marked "make"' in status['message']: # orbit failed, so..
									conf[cur_label]['done'] == '0'	# Group not done
						else:
							cur_group = 0
							conf['cur_group'] = '%d'%cur_group# Re-initialize cur_group

						status['conf'] = conf
						status = skyd_write_conf(cffile,status)

					if not done:
						status['message'] = cur_label+' '+status['message']

		# If boss_pid as specified it must be the process id of the daemon.
		# SIGUSR1 tells the daemon to start the next orbit

		# Would be nice to check here that the daemon is still running
		# (i.e. that boss_pid is still a valid process id)

		if status['number'] == 0:
			result = 'done'
		else:
			result = 'kill'

		open(report+'_'+result,'w').write(status['message']+'\n')

		say('skyd_orbit','I',wmark,'%s, "%s" pid %d -> "%s" pid %d'%(result,grunt,grunt_pid,boss,boss_pid))

		sleep(5)

		if boss == grunt:			# skyd_wait running on same machine
			os.kill(boss_pid,SIGUSR1)
		else:
			result = (os.popen('ssh '+boss+' "kill -%d %d"'%(SIGUSR1,boss_pid))).read()
			say('skyd_orbit','I',wmark,result)

	else:

		status['args'] = dict        \
				( [                             \
				( 'camera'		, start   ( '-camera='		,sys.argv ) ),	\
				( 'mode'		, start   ( '-mode='  		,sys.argv ) ),	\
				( 'keepglare'	, is_there( '-keepglare'	,sys.argv ) ),	\
				( 'overwrite'	, is_there( '-overwrite'	,sys.argv ) ),	\
				( 'checkversion', is_there( '-checkversion'	,sys.argv ) ),	\
				( 'avoidsun'	, is_there( '-avoidsun'		,sys.argv ) ),	\
				( 'avoidmoon'	, is_there( '-avoidmoon'	,sys.argv ) ),	\
				( 'ignore_cat'	, is_there( '-ignore_cat'	,sys.argv ) ), 	\
				( 'nped_min'	, start   ( '-nped_min='	,sys.argv ) ),	\
				( 'ndark_min'	, start   ( '-ndark_min='	,sys.argv ) ),	\
				( 'sdark'		, start   ( '-sdark='		,sys.argv ) ),  \
				( 'level'		, start   ( '-level='		,sys.argv ) ),	\
				( 'cur_orbit'	, start   ( '-orbit=' 		,sys.argv ) ),	\
				( 'min_orbit'	, start   ( '-min_orbit='	,sys.argv ) ),	\
				( 'max_orbit'	, start   ( '-max_orbit='	,sys.argv ) ),	\
				( 'catalogue'	, start   ( '-catalogue='	,sys.argv ) ),	\
				( 'source'		, start   ( '-source='		,sys.argv ) ),	\
				( 'destination'	, start   ( '-destination='	,sys.argv ) ),	\
				( 'alltheway'	, is_there( '-alltheway'	,sys.argv ) ),	\
				( 'keepbkgnd'	, is_there( '-keepbkgnd'	,sys.argv ) )	\
				] )

		status = skyd_orbit( status )

	sys.exit(status['number'])
