import time
import appuifw
import e32
import key_codes
import btsocket
import struct
import locale

output_list=[u"Version 2013.1"]
def output_to_list(s):
	output_list.append(s)
	appuifw.app.body.set_list(output_list,len(output_list)-1)

output=output_to_list

def seconds_to_string(aseconds):
	seconds=int(aseconds)
	miliseconds=aseconds-seconds
	hours=seconds/3600
	seconds-=hours*3600
	minutes=seconds/60
	seconds-=minutes*60
	return "%02d:%02d:%02.3f"%(hours,minutes,(seconds+miliseconds))

class Statistics:
	def __init__(self):
		self.data={}

	def add_average(self,label):
		self.data[label]={"min":0.0,"max":0.0,"moving_avg":0.0,"overall_avg":0.0,"moving_count":0,"overall_count":0}

	def add_accumulator(self,label,value=0.0):
		self.data[label]=value

	def reset_variable(self,label=[],value=0.0):
		if label!=[]: labels=label
		else: labels=self.data
		for l in labels:
			if isinstance(self.data[l],dict):
				self.add_average(l)
			elif isinstance(self.data[l],float):
				self.add_accumulator(l,value)

	def update_variable(self,label,value,speed=0.0):
		if isinstance(self.data[label],dict):
			if value>0 and(self.data[label]["min"]>value or self.data[label]["min"]==0):
				self.data[label]["min"]=value
			if self.data[label]["max"]<value:
				self.data[label]["max"]=value
			if speed>0:
				self.data[label]["moving_avg"]+=value
				self.data[label]["moving_count"]+=1
			self.data[label]["overall_avg"]+=value
			self.data[label]["overall_count"]+=1
		elif isinstance(self.data[label],float):
			self.data[label]+=value

	def nonzero_variables(self):
		result=[]
		for l in self.data:
			if isinstance(self.data[l],float) and self.data[l]>0.0:
				result.append(l)
			elif isinstance(self.data[l],dict):
				a=0.0
				for x in self.data[l]:
					a+=self.data[l][x]
				if a>0.0: result.append(l)
		return result

	def show_average(self,label):
		if isinstance(self.data[label],dict) and self.data[label]["overall_count"]>0:
			return u"overall average: %.3f"%(self.data[label]["overall_avg"]/self.data[label]["overall_count"])
		else: return u"Cannot calculate"

	def generate_average_report(self,label):
		out=[]
		if not label in self.data or not isinstance(self.data[label],dict): return out
		out.append(u"Min: %02.3f"%self.data[label]["min"])
		out.append(u"Max: %02.3f"%self.data[label]["max"])
		if self.data[label]["moving_count"]>0:
			out.append(u"Moving average: %02.3f"%(self.data[label]["moving_avg"]/self.data[label]["moving_count"]))
		if self.data[label]["overall_count"]>0:
			out.append(u"overall average: %02.3f"%(self.data[label]["overall_avg"]/self.data[label]["overall_count"]))
		return out

	def show_accumulator(self,label):
		if label in self.data and isinstance(self.data[label],float):
			return u"%s: %.3f"%(label,self.data[label])
		else: return u""

class Stopwatch:


	def __init__(self):
		self._start_time=self._last_time=-1;

	def start(self,t=-1.0):
		self._start_time=self._last_time=time.time()
		if t>0.0:
			self._start_time-=t

	def raw_time(self):
		if self._start_time==-1:
			return 0.0
		return time.time()-self._start_time

	def time(self,pt=-1):
		if pt==-1:
			t=self.raw_time()
		else:
			t=pt
		if t==0.0:
			return u"Stopwatch: stopped"
		elif pt>-1:
			return u"Stopped at: "+seconds_to_string(t)
		return u"Ellapsed: "+seconds_to_string(t)

	def lap(self):
		if self._start_time==-1:
			return u"Stopwatch: stopped"
		tmp,self._last_time=self._last_time,time.time()
		return u"Lap: "+seconds_to_string(self._last_time-tmp)

class ZephirHXM:

	def __init__(self):
		self.reset()

	def reset(self):
		self.heartbeat=0
		self.battery=0
		self._beat_number=0
		self.distance=0
		self.raw_distance=-1
		self.speed=0

	def running(self):
		return (self.raw_distance!=-1)

	def update(self,data):
		if len(data)<59 or ord(data[2])!=55:
			return False
		self.heartbeat=ord(data[12])
		self.battery=ord(data[11])
# beat number 0 to 255, 
		self._beat_number=ord(data[13])
# Data in 50 is distance, valid range 0..256 m
		byte2=struct.unpack_from('h',data,50)[0]
		byte2=byte2/16
		if self.raw_distance==-1:
			self.raw_distance=byte2
		elif byte2>=self.raw_distance:
			self.distance=(byte2-self.raw_distance)
		else:
			self.distance=(255-self.raw_distance+byte2)
		self.raw_distance=byte2
# Data in 52 is instanteous speed, 0 - 15,996 m/s multiplycation by 3.6 converts it to km/h
		byte2=struct.unpack_from('h',data,52)[0]
		self.speed = float(byte2/256*3.6)
		return True

class Main:

# property initialization
	stopwatch=None
	hrm=None

# menu item callbacks
	def stopwatch_start(self):
		if not self.stopwatch:
			self.stopwatch=Stopwatch()
			if self.stats.data["countdown_time"]>0:
				self.countdown_timer=e32.Ao_timer()
				self.countdown_callback(True)
			self.stopwatch.start(self.stats.data["stopwatch_time"])
			output(u"Running")
		else:
			t=self.stopwatch.raw_time()
			if self.stats.data["countdown_time"]>0:
				self.countdown_timer.cancel()
				self.stats.update_variable("countdown_time",-1.0*t)
			e32.reset_inactivity()
			output(self.stopwatch.time(t))
			self.stopwatch=None
			self.stats.add_accumulator("stopwatch_time",t)
		self.make_menu()

	def stopwatch_time(self):
		if not self.repeating["enabled"]: self.repeating["function"]=self.stopwatch_time
		output(self.stopwatch.time())

	def stopwatch_lap(self):
		if not self.repeating["enabled"]: self.repeating["function"]=self.stopwatch_lap
		output(self.stopwatch.lap())

	def stopwatch_countdown(self):
		a=appuifw.query(u"countdown (seconds):","number",int(self.stats.data["countdown_time"]))
		if a:
			self.stats.add_accumulator("countdown_time",float(a))

	def hrm_connect(self):
		if not self.hrm:
			self.sock=btsocket.socket(btsocket.AF_BT,btsocket.SOCK_STREAM)
			address,services=btsocket.bt_discover()
			if not address:
				return
			self.sock.connect((address,1))
			self.hrm_timer=e32.Ao_timer()
			self.hrm=ZephirHXM()
			output(u"HRM: connected")
			self.hrm_callback()
		else:
			output(u"HRM: connection closed.")
			self.sock.close()
			appuifw.note(u"HRM: connection lost.","error")
			self.hrm=None
		self.make_menu()

	def hrm_heartbeat(self):
		if not self.hrm: return
		if not self.repeating["enabled"]: self.repeating["function"]=self.hrm_heartbeat
		output(u"Heartbeat: %d"%self.hrm.heartbeat)

	def hrm_average_heartbeat(self):
		if not self.repeating["enabled"]: self.repeating["function"]=self.hrm_average_heartbeat
		output(self.stats.show_average("hrm_heartbeat"))

	def hrm_battery(self):
		if not self.hrm: return
		output(u"HRM Battery: %d%%"%self.hrm.battery)

	def hrm_speed(self):
		if not self.hrm: return
		if not self.repeating["enabled"]: self.repeating["function"]=self.hrm_speed
		output(u"HRM Speed: %.3f"%self.hrm.speed)

	def hrm_average_speed(self):
		if not self.repeating["enabled"]: self.repeating["function"]=self.hrm_average_speed
		output(self.stats.show_average("hrm_speed"))

	def hrm_distance(self):
		if not self.repeating["enabled"]: self.repeating["function"]=self.hrm_distance
		output(self.stats.show_accumulator("hrm_distance"))

	def stats_reset(self):
		list=self.stats.nonzero_variables()
		if list==[]: return
		list.insert(0,u"All")
		out=appuifw.multi_selection_list([unicode(x) for x in list])
		if not out: return
		if 0 in out:
			self.stats.reset_variable(list[1:])
		else:
			self.stats.reset_variable([list[x] for x in out])

	def stats_report(self):
		global output_list
		out=[u"Statistics:"]
		if self.stopwatch:
			self.stats.add_accumulator("stopwatch_time",self.stopwatch.raw_time())
		out.append(u"time: %s"%seconds_to_string(self.stats.data["stopwatch_time"]))
		out.append(u"hrm_heartbeat")
		out+=self.stats.generate_average_report("hrm_heartbeat")
		out.append(u"hrm_speed")
		out+=self.stats.generate_average_report("hrm_speed")
		out.append(self.stats.show_accumulator("hrm_distance"))
		x=len(output_list)
		output_list+=out
		appuifw.app.body.set_list(output_list,x)

	def switch_output(self):
		global output
		self.old_output,appuifw.app.body=appuifw.app.body,self.old_output
		if output==output_to_list: output=appuifw.note
		else: output=output_to_list

	def toggle_repeating(self):
		if not self.repeating["function"]:
			appuifw.note(u"Don't know what to repeat")
			return
		self.repeating["enabled"]=not self.repeating["enabled"]
		if not self.repeating["enabled"]:
			appuifw.note(u"Repeating disabled")
			self.repeating_timer.cancel()
		else:
			appuifw.note(u"repeating function %s"%self.repeating["function"].__name__)
			self.repeating_timer=e32.Ao_timer()
			self.repeating_callback()
		self.make_menu()

	def app_quit(self):
		self.app_lock.signal()
		appuifw.app.set_exit()

# other callbacks
	def list_callback(self):
		pass

	def hrm_callback(self):
		try:
			data=self.sock.recv(1024)
		except:
			output(u"HRM: connection lost.")
			self.sock.close()
			appuifw.note(u"HRM: connection lost.","error")
			self.hrm=None
			self.make_menu()
			return
		if self.hrm.update(data) and self.stopwatch:
			self.stats.update_variable("hrm_distance",self.hrm.distance)
			self.stats.update_variable("hrm_heartbeat",self.hrm.heartbeat,self.hrm.speed)
			self.stats.update_variable("hrm_speed",self.hrm.speed,self.hrm.speed)
		self.hrm_timer.after(1,self.hrm_callback)

	def repeating_callback(self):
		if not self.repeating["enabled"]: return
		e32.reset_inactivity()
		self.repeating["function"]()
		self.repeating_timer.after(5,self.repeating_callback)

	def countdown_callback(self,first=False):
	# following lines are here because of timer interval limit (2000 seconds)
		if not first: # is this a first call (from stopwatch_start)
			self.stats.update_variable("countdown_time",-2000.0) # not first, called after 2000 seconds
		if self.stats.data["countdown_time"]>2000: # run this function after 2000 seconds
			self.countdown_timer.after(2000.0,self.countdown_callback)
		else: # We can finalize timer and call stopwatch_start to stop stopwatch
			self.countdown_timer.after(self.stats.data["countdown_time"],self.stopwatch_start)


# Other routines
	def make_menu(self):
		if self.stopwatch:
			menu_stopwatch=((u"Stop",self.stopwatch_start),(u"time",self.stopwatch_time),
				(u"Lap",self.stopwatch_lap))
		else:
			menu_stopwatch=((u"start",self.stopwatch_start),(u"Countdown",self.stopwatch_countdown))
		if self.hrm:
			menu_hrm=((u"Disconnect",self.hrm_connect),
				(u"Heartbeat",self.hrm_heartbeat),(u"Average Heartbeat",self.hrm_average_heartbeat),
				(u"Speed",self.hrm_speed),(u"Average Speed",self.hrm_average_speed),
				(u"Distance",self.hrm_distance),(u"Battery",self.hrm_battery))
		else:
			menu_hrm=((u"connect",self.hrm_connect),
				(u"Average Heartbeat",self.hrm_average_heartbeat),(u"Average Speed",self.hrm_average_speed),
				(u"Distance",self.hrm_distance))
		menu_stats=((u"reset",self.stats_reset),(u"Show",self.stats_report))
		if self.repeating["enabled"]:
			repeatingstr=u"Disable Repeating"
		else:
			repeatingstr=u"Enable Repeating"
		appuifw.app.menu=[(u"Stopwatch",menu_stopwatch),(u"hrm",menu_hrm),
			(u"Statistics",menu_stats),(repeatingstr,self.toggle_repeating),
			(u"Exit",self.app_quit),(u"dbg",self.switch_output)]

	def main(self):
		self.stopwatch=None
		self.hrm=None
		self.repeating={"enabled":False,"function":None}
		self.stats=Statistics()
		self.stats.add_accumulator("stopwatch_time")
		self.stats.add_accumulator("countdown_time")
		self.stats.add_average("hrm_speed")
		self.stats.add_average("hrm_heartbeat")
		self.stats.add_accumulator("hrm_distance")
		locale.setlocale(locale.LC_ALL, '')
		appuifw.app.title = u"Talking HRM"

		appuifw.app.body,self.old_output=appuifw.Listbox(output_list,self.list_callback),appuifw.app.body
		appuifw.app.body.bind(key_codes.EKey1,self.stopwatch_time)
		appuifw.app.body.bind(key_codes.EKeySelect,self.stopwatch_start)
		appuifw.app.body.bind(key_codes.EKey4,self.stopwatch_lap)
		appuifw.app.body.bind(key_codes.EKey0,self.hrm_heartbeat)
		appuifw.app.body.bind(key_codes.EKeyHash,self.hrm_average_heartbeat)
		appuifw.app.body.bind(key_codes.EKey8,self.hrm_speed)
		appuifw.app.body.bind(key_codes.EKey9,self.hrm_average_speed)
		appuifw.app.body.bind(key_codes.EKeyStar,self.hrm_distance)
		appuifw.app.body.bind(key_codes.EKey3,self.toggle_repeating)

		self.make_menu()
#Set the function to be called when the right softkey is pressed (quit)
		appuifw.app.exit_key_handler = self.app_quit;

#Create an instance of Ao_lock - an active object based synchronization service
		self.app_lock = e32.Ao_lock()
#Wait for application lock to be signalled
		self.app_lock.wait()

if __name__ == '__main__':
	Main().main()
