#!/usr/local/bin/python3
#given a list of names, construct the best use of time for watches
#in 5e: each individual must achieve six hours of sleep in an eight hour period
#ignoring elves
#Jerry Stratton astoundingscripts.com

from sys import argv, exit
from itertools import combinations, permutations
from math import ceil

characters = argv
characters.pop(0)

if len(characters) == 0:
	print("watches [watch count or character list]")
	exit()

class Watch:
	periodsPerPerson = 1

	def __init__(self, characters):
		if len(characters) == 1:
			self.characters = []
			characterCount = int(characters[0])
		else:
			self.characters = characters
			characterCount = len(characters)
		self.setPeriods(characterCount)
		self.validate()

	def validate(self):
		if self.periods < 2:
			print("There must be at least two characters to have a watch.")
			exit()
		return True

	def setPeriods(self, characterCount):
		self.periods = characterCount

	def __str__(self):
		return str(self.duration) + "/" + str(self.total)

	def __getattr__(self, name):
		if name == 'duration':
			return self.hoursToTime(self.getWatchDuration())
		if name == 'personalDuration':
			return self.hoursToTime(self.getWatchDuration()*self.periodsPerPerson)
		if name == 'period':
			return self.getWatchDuration()
		if name == 'periods':
			return self.periods
		if name == 'total':
			return self.hoursToTime(self.period*self.periods)
		if name == 'sleep':
			return self.getSleep()

	def hoursToTime(self, hoursWithMinutes):
		hours = int(hoursWithMinutes)
		minutes = (hoursWithMinutes-hours)*60
		return "%02i:%02i" % (hours, minutes)

	def getSleepPeriods(self):
		return self.periods-self.periodsPerPerson

	def getSleep(self):
		return self.hoursToTime(self.period*(self.getSleepPeriods()))

	def getWatchDuration(self):
		#optimal watch length if everything else works out
		watchLength = 6.0/(self.getSleepPeriods())

		#does this combine to be at least 8 hours of rest?
		if watchLength*self.periods < 8:
			watchLength = 8.0/self.periods

		#does each character get the necessary rest, interrupted by no more than two hours?
		if watchLength*self.periodsPerPerson > 2:
			if self.characters:
				for index in range(0, len(self.characters)):
					sleepPeriods = self.getCharacterSleeps(index)
					sleepNeeded = self.getSleepNeeded(index)
					sleepTime = sleepPeriods*watchLength
					if sleepTime < sleepNeeded:
						watchLength = sleepNeeded/sleepPeriods
			else:
				sleepPeriods = ceil(self.getSleepPeriods()/2)
				sleepNeeded = self.getSleepNeeded(sleepPeriods)
				sleepTime = sleepPeriods*watchLength
				if sleepTime < sleepNeeded:
					watchLength = sleepNeeded/sleepPeriods
		return watchLength

	def startTime(self, period):
		startTime = period*self.getWatchDuration()
		return self.hoursToTime(startTime)

	def durationBug(self):
		print("Watch:\t%s" % watch.duration)
		if self.periodsPerPerson > 1:
			print("Full watch:\t%s" % watch.personalDuration)
		print("Sleep time:\t%s" % watch.sleep)
		print("Rest period:\t%s" %watch.total)
		print()

	def listWatches(self):
		if self.characters:
			for period in range(0, self.periods):
				print("watch %i\t%s\t%s" % (period+1, self.getWatchers(period), self.startTime(period)))
			print("end\t\t%s" % (self.total))

	def getCharacter(self, index):
		character = self.characters[index]
		if character.startswith('-'):
			character = character[1:]
		return character

	def getSleepNeeded(self, index):
		if self.characters and self.characters[index].startswith('-'):
			return 4
		else:
			return 6

	def watcherList(self, period):
		return (self.getCharacter(period),)

	def getWatchers(self, period):
		return self.getCharacter(period)

	def getCharacterSleeps(self, index):
		character = self.characters[index]
		sleepSides = [0,0]
		sleepSide = 0
		for period in range(0, self.periods):
			onWatch = self.watcherList(period)
			if character not in onWatch:
				sleepSides[sleepSide] += 1
			elif sleepSide == 0:
				sleepSide += 1
		return max(sleepSides)


class StaggeredWatch(Watch):
	periodsPerPerson = 2

	def __init__(self, watchCount):
		super(StaggeredWatch, self).__init__(watchCount)

	#you need eight characters for simple staggered watches
	#though some combinations down to four are doable
	def validate(self):
		if self.periods > 2 and self.period*self.periods < 24:
			return True
		return False

	def watcherList(self, period):
		character1 = period
		character2 = period+1
		if character2 >= self.periods:
			character2 = 0
		return (self.getCharacter(character1), self.getCharacter(character2))

	def getWatchers(self, period):
		watchers = self.watcherList(period)
		return watchers[0] + '/' + watchers[1]

class DoubledWatch(Watch):
	#double watches almost never make sense
	#but they are doable with 4 or more characters
	def validate(self):
		if self.periods < 2:
			return False
		return True

	def setPeriods(self, characterCount):
		self.periods = int(characterCount/2)

	def getWatcherList(self, period):
		character1 = period*2
		character2 = period*2+1
		return (self.getCharacter(character1), self.getCharacter(character2))

	def getWatchers(self, period):
		watchers = self.getWatcherList(period)
		return watchers[0] + '/' + watchers[1]

#single-person watches
watch = Watch(characters)
print("# %i individual watches" % watch.periods)
watch.durationBug()
watch.listWatches()

#construct staggered watches
watch = StaggeredWatch(characters)
if watch.validate():
	print()
	print("# %i staggered watches" % watch.periods)
	watch.durationBug()
	watch.listWatches()

#construct double watches
watch = DoubledWatch(characters)
if watch.validate():
	print()
	print("# %i double watches" % watch.periods)
	watch.durationBug()
	watch.listWatches()
