
import defensive

log = defensive.Logging('fsm')

def state(stateName, transitionList=[],default=None):
	transition = tuple(transitionList)
	@log.debug
	def closure(flagLst):
		for (trigger,nextState) in transition:
			if trigger in flagLst:
				return nextState
		if not default:
			return stateName
		return default
	#closure.__name__=stateName
	return closure

@log.debug
def _stateFromJson(data={}):
	"""
	json:
	{
		"name":string,
		"transition":{"triggerPredicateName":"nextStateName"},
		"default":"defaultStateTransition"
	}
	will return state closure
	"""
	stateName = data['name']
	transitionList = data['transition'].items() if 'transition' in data else []
	default = data['default'] if 'default' in data.keys() else stateName
	return state(stateName,transitionList,default)

@log.debug
def fromJson(data=[]):
	"""
	json:[{...see _stateFromJson ...},{...}]
	will return dictionnary by state name containing all state
	"""
	stateMachine = {}
	for subData in data:
		state = _stateFromJson(subData)
		stateMachine[subData['name']]=state
	return stateMachine

if __name__ == '__main__':
	import logging
	log.logger.setLevel(logging.DEBUG)
	log.logger.addHandler(logging.StreamHandler())
	test= defensive.Testing()

	machine = [
		{	'name':'A',	'transition':{'everyFiveSec':'B','everySevenSec':'C'}},
		{	'name':'B',	'transition':{},'default':'A'},
		{	'name':'C',	'transition':{'everyThreeSec':'D'}},
		{	'name':'D',	'transition':{}	}
	]
	time = 1
	def everyThreeSec():
		return time%3==0
	def everyFiveSec():
		return time%5==0
	def everySevenSec():
		return time%7==0
	triggersuite={
		'everyThreeSec':everyThreeSec,
		'everyFiveSec':everyFiveSec,
		'everySevenSec':everySevenSec
	}
	fsm = fromJson(machine)
	
	@test.eq('B')
	def transitionA5sec():
		global time
		time = 'everyFiveSec'
		return fsm['A']([time])

	@test.eq('A')
	def transitionA3sec():
		global time
		time = 'everyThreeSec'
		return fsm['A']([time])

	@test.eq('C')
	def transitionA7sec():
		global time
		time = 'everySevenSec'
		return fsm['A']([time])

	@test.eq('C')
	def transitionA15sec():
		global time
		time = 'everySevenSec'
		return fsm['A']([time])

	@test.eq('A')
	def transitionB():
		return fsm['B']([time])

	@test.eq('D')
	def transitionC3sec():
		global time
		time = 'everyThreeSec'
		return fsm['C']([time])

	@test.eq('C')
	def transitionC5sec():
		global time
		time = 'everyFiveSec'
		return fsm['C']([time])
	
	test.runTest()
