Exemplo n.º 1
0
	def insert(self, node, rule, time, ruleChainHour):
		'''
		Recursive functions can get really tricky really quickly sticky.
		
		Ideally, this docstring would contain some helpful hints. Here's some
		terminology:
		"this node" - the parameter called node
		"this rule" - the parameter called rule, not this node's rule
		
		Once upon a time, the code for this function was short and concise.
		Then the dawning of the "duration" attribute appeared. With this
		attribute, a constant and a rule of variants can overlap and overwrite
		other rules. The code is now much more complex, because each hour has
		to be properly partitioned by rules defined in all hours; and not only
		this, but this partitioned time has to be represented by linked lists
		and everything has to occur recursively.
		
		Keep in mind: Constants ALWAYS have a duration (if it isn't explicitly
		declared, then it defaults to the GCD). Rule with no duration are
		considered to be rules with INFINITE duration. Variant rules CAN have a
		duration; this acts to give them a lower priority in times past the end
		of the duration and a higher priority in times during the duration.
		
		The return value of this function is a RuleNode which (in the first
		level deep) becomes the new root node for that hour in the RuleChain.
		Therefore, it is helpful to think of the return value as a way to
		modify the parent node's next node.
		'''
		# If true, rule will override node during conflicts
		precedence = ruleChainHour >= time.hours
		
		if node == None:
			# Base case: insert a new node into the rule chain by creating it
			# and setting its child to this node.
			return RuleNode(rule, time, node)
		
		# Assume the same hour as the node so comparisons will work
		tempTime = Time(node.time.hours, time.minutes, time.seconds)
		tempTime.duration = time.duration
		
		# At the highest level, we consider three cases: the time attached to
		# this rule is either before, equal to, or after the time of this node.
		
		if tempTime.toSeconds() < node.time.toSeconds():
			# Time occurs before this node. In all cases, the rule is prepended
			# to the chain. Keep in mind, a time with no duration is basically
			# a time with infinite duration. Also keep in mind, Constants
			# ALWAYS have a duration.
			if not time.duration:
				return RuleNode(rule, time, node)
			
			# Three cases: rules don't overlap, rules overlap partially, rules overlap fully
			# Case 1: rules don't overlap
			if tempTime.end() <= node.time.toSeconds():
				return RuleNode(rule, time, node)
			
			# Case 2: rules overlap partially
			if tempTime.end() < node.time.end():
				if precedence:
					# Move node into the furture and shorten its duration
					newBeginning = Time.fromSeconds(tempTime.end())
					newDuration = node.time.duration.toSeconds() - (node.time.end() - tempTime.end())
					newBeginning.duration = Time.fromSeconds(newDuration)
					node.time = newBeginning
					return RuleNode(rule, time, node)
				else:
					# Shorten time
					time.duration = Time.fromSeconds(node.time.toSeconds() - tempTime.toSeconds())
					return RuleNode(rule, time, node)
			
			# Case 3: node is fully overlapped by rule
			# time.end() >= node.time.end()
			if precedence:
				# Not including this node in the return statement effectively
				# eliminates it. However, things aren't this simple. We need to
				# check if the next node is partially/fully consumed via
				# recursion.
				return self.insert(node.next, rule, time, ruleChainHour)
			else:
				# Split the rule into two nodes that fall on either side of
				# this node. We create the following chain:
				# parent node -> node1 -> node -> node2 -> node.next
				time1 = time.copy()
				time1.duration = Time.fromSeconds(node.time.toSeconds() - tempTime.toSeconds())
				node1 = RuleNode(rule, time1, node)
				time2 = Time.fromSeconds(node.time.end())
				time2.hours = time.hours # Use original hours to maintain precedence
				time2.duration = Time.fromSeconds(tempTime.end() - node.time.end())
				# Use recursion, because time2 might extend past node.next
				node2 = self.insert(node.next, rule, time2, ruleChainHour)
				node.next = node2
				return node1
		
		# The case where rule occured before node was relatively straightforward.
		# Now, rule and node occur at the same time, which means that most
		# likely either rule or node is omitted.
		if tempTime.toSeconds() == node.time.toSeconds():
			if not precedence:
				# Ignore the rule
				return node
			
			# We've established that the rule has precedence. Now it's just a
			# matter of finding out how much of node (and its children) to
			# delete.
			
			# Three cases
			# Case 1: No rule duration
			if not tempTime.duration:
				# Replace the node
				return RuleNode(rule, time, node.next)
			
			# Case 2: Rule duration, but no node duration
			if not node.time.duration:
				# Peek ahead at the future node
				if node.next:
					tempTime2 = Time(node.next.time.hours, time.minutes, time.seconds)
					tempTime2.duration = time.duration
					if tempTime2.end() > node.next.time.toSeconds():
						# This node is fully engulfed. Replace the node, and
						# make sure that we replace any other overlapped nodes
						# (recursively, of course)
						# parent node -> node1 -> node.next
						node1 = self.insert(node.next, rule, time, ruleChainHour)
						return node1
				# Make this node start at the end of this rule
				node.time = Time.fromSeconds(tempTime.end())
				return RuleNode(rule, time, node)
			
			# Case 3: Rule duration AND node duration. Let the battle begin!
			if tempTime.end() >= node.time.end():
				# Replace the node and any following nodes if necessary
				node1 = self.insert(node.next, rule, time, ruleChainHour)
				return node1
			else:
				# Chop off and preserve the dangling part
				end = node.time.end()
				node.time = Time.fromSeconds(tempTime.end())
				# node.time.hours is already set because tempTime.hours was copied earlier
				node.time.duration = Time.fromSeconds(end - tempTime.end())
				return RuleNode(rule, time, node)
		
		# Rule occurs in the future: tempTime.toSeconds() > node.time.toSeconds()
		
		# Three cases: rules don't overlap, rules overlap partially, rules overlap fully
		# Case 1
		if node.time.end() <= tempTime.toSeconds():
			# If node.rule is a constant, it doesn't need to persist after its duration
			if not tempTime.duration or self.isConstant(node.rule):
				# Regular rule or node is constant, recurse deeper
				node.next = self.insert(node.next, rule, time, ruleChainHour)
				return node
			else:
				# Peek ahead at the future node
				if node.next:
					tempTime2 = Time(node.next.time.hours, time.minutes, time.seconds)
					tempTime2.duration = time.duration
					if tempTime2.toSeconds() > node.next.time.toSeconds():
						# Next node is in the future too, recurse deeper
						node.next = self.insert(node.next, rule, time, ruleChainHour)
						return node
				
				# tempTime has a duration so node should persist after rule's completion
				# To do so, we dupe node and let recursion do the rest
				timeCopy = Time.fromSeconds(tempTime.toSeconds())
				if node.time.duration:
					# Need to modify duration of both old and new time
					timeCopy.duration = Time.fromSeconds(node.time.end() - tempTime.toSeconds())
					node.time = node.time.copy()
					node.time.duration = Time.fromSeconds(tempTime.toSeconds() - node.time.toSeconds())
				nodeCopy = RuleNode(node.rule, timeCopy, node.next)
				node.next = self.insert(nodeCopy, rule, time, ruleChainHour)
				return node
		
		# Implicit that node.time.duration exists
		# Case 2
		if tempTime.end() > node.time.end():
			# Implicit that tempTime.duration exists
			if precedence:
				# Shorten node
				node.time = node.time.copy()
				node.time.duration = Time.fromSeconds(tempTime.toSeconds() - node.time.toSeconds())
				# Link in the new node after this one
				node.next = RuleNode(rule, time, node.next)
				return node
			else:
				# Shorten rule by giving it a later starting time
				time2 = Time.fromSeconds(node.time.end())
				time2.hours = time.hours # Use original hours to maintain precedence
				time2.duration = Time.fromSeconds(tempTime.end() - node.time.end())
				node.next = RuleNode(rule, time2, node.next)
				return node
		
		# Case 3: node.time has a duration and tempTime occurs in the middle of it
		if not precedence:
			if tempTime.duration:
				# tempTime is fully engulfed by node
				return node
			else:
				# tempTime is a rule, so it continues past node
				time2 = Time.fromSeconds(node.time.end())
				node.next = self.insert(node.next, rule, time2, ruleChainHour)
				return node
		if not tempTime.duration:
			# tempTime is a regular rule, so chop node in half and occupy the
			# second half with this rule
			node.time = node.time.copy()
			node.time.duration = Time.fromSeconds(tempTime.toSeconds() - node.time.toSeconds())
			# Link in the new node after this one
			node.next = RuleNode(rule, time, node.next)
			return node
		if tempTime.end() == node.time.end():
			# Ends coincide. Things are easy, just chop and link
			node.time = node.time.copy()
			node.time.duration = Time.fromSeconds(tempTime.toSeconds() - node.time.toSeconds())
			# Link in the new node after this one
			node.next = RuleNode(rule, time, node.next)
			return node
		# Things are messy. Bisect node:
		# parent node -> node(1) -> rule -> node(2) -> node.next
		# Go from last to first. Start with second node:
		time2 = Time.fromSeconds(tempTime.end())
		time2.duration = Time.fromSeconds(node.time.end() - tempTime.end())
		node2 = RuleNode(node.rule, time2, node.next)
		newNode = RuleNode(rule, time, node2)
		time1 = node.time.copy()
		time1.duration = Time.fromSeconds(tempTime.toSeconds() - node.time.toSeconds())
		node.time = time1
		node.next = newNode
		return node