#!/usr/bin/python

from time import sleep
import time
import threading
import subprocess
import os
import sys
from random import randint
from threading import Thread
from datetime import datetime, timedelta
import pigpio # http://abyz.co.uk/rpi/pigpio/python.html

from interface    import Interface
from tagsReader   import TagsReader
from update       import Update
from wifi         import Wifi
from logs         import Logs
from lora         import Lora
from live         import Live
from live_coap		import Live_coAP
from config       import Config

from nbiot		import NBiot

gui = []
up  = []
wifi= []
tr  = []
lg  = []
lora = []
li = []
nbiot= []

versionid = '0320'
versiondesc = 'Writes to API with Live via Nbiot, contains more logging protections,flush logs to sd'

loraEnable = False
liveEnable = False
nbiotEnable = False

startNbiotTS = 0;

###################################################################################
#                MAIN
###################################################################################
# NAME        : main
# DESCRIPTION : starts the system, handles received tags and graphical interface
###################################################################################
def main():
	sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
	global gui, up, tr, lg, wifi, lora, li, versionid, versiondesc, loraEnable,liveEnable,nbiot
	
	# checks if downloaded new data
	# this is not a new firmware
	up = Update()
	v_newData, m_newData = up.loadNewData()

	print('Version' + versionid + ' Lora module V3 ' + versiondesc)
	gui = Interface()

	# read configuration file
	conf = Config()
	c_lora = conf.getLora()
	if c_lora is not None and c_lora['enable']:
		loraEnable = True

	c_live = conf.getLive()
	if c_live is not None and c_live['enable']:
		liveEnable = True

	c_nbiot = conf.getNBiot()
	if c_nbiot is not None and c_nbiot['enable']:
		nbiotEnable = True

	lg = Logs(up.getBeamianId())
	wifi = Wifi()
	lora = Lora(up.getBeamianId(), up.getMac())
	
	if nbiotEnable:
		li = Live_coAP(up.getMac())
	else:
		li = Live(up.getMac())

	sendLora = None
	sendLive = None

	if loraEnable:
		sendLora = lora.writeLineLora
	if liveEnable:
		sendLive = li.sendLineLive

	tr = TagsReader(up.getId(), lg.writeLine, sendLora, sendLive)

	gui.setId('ID: '+ up.getId())

	c_stand = conf.getStand()
	if c_stand is not None and c_stand['enable']:
		gui.setCompanyName(c_stand['name'])

	c_logo = conf.getLogo()
	if c_logo is not None and c_logo['enable']:
		gui.setLogo('data/' + c_logo['path'])

	c_beep = conf.getBeep()
	if c_beep is not None and c_beep['enable'] == False:
		gui.setBeep(False)
	else:
		gui.setBeep(True)

	c_autotest = conf.getAutoTest()
	if c_autotest is not None and c_autotest['enable']:
		automaticTestEnable = True
	else:
		automaticTestEnable = False

	gui.setWifi(False)
	gui.setLora(False) #True==Visible, False==Hidden; -1 = Disconnected, 0 = Avaiable, 1 = Connected

	if firstRun():
		gui.setBeepFreq(4200)
		gui.makeBeep()
		sleep(0.1)
		gui.setBeepFreq(6000)
		gui.makeBeep()

		gui.setPopup("Checking new image")
		sleep(1)
		gui.updateScreenID()
		sleep(1)
		gui.updateScreen()
		firstRunSuccess()
		gui.stop()
		tr.close()
		exitRestartApp()


	#executeShellCommand()

	gui.setCounts(lg.numberLogsInFile())
	lg.getCurrentLogCount()

	#print('ID: ' + up.getBeamianId() + ' MAC: ' + up.getMac())
	gui.setPopup('ID:' + up.getBeamianId() + '\n' + up.getId() + '\n\nMAC:\n' + up.getMac()[0:8] + '\n' + up.getMac()[9:17], 2)

	gui.setBeepFreq(6000)
	gui.makeBeep()
	sleep(0.1)
	gui.setBeepFreq(4200)
	gui.makeBeep()

	if v_newData:
		gui.setPopup(m_newData, 2)

	autoBoot(True, liveEnable)

	if nbiotEnable:
		nbiot = NBiot()
	startNbiotTS = time.time()
	
	print("time start" + str(startNbiotTS))

	if loraEnable:
		print("Use LoRa to send tags")
		# use lora
		if lora.isLoraPresent():
			print('LoRa module is present')
			gui.setLora(True, -1)
			connectLora(None)

	if liveEnable:
		keepWifi()
	try:
		while True:
			
			sleep(0.3)
			code, msg = tr.waitAndSort()

			if nbiotEnable:
				if not nbiot.isConnected() or nbiot.isConnecting():
					newTS = time.time()	
					if (newTS > startNbiotTS+30):
						print("time check" + str(newTS))
						connectNbiot(nextFunction=None, force=False, symbol=True)


			if loraEnable:
				updateGuiLora()
				checkLoraThreadStatus()

			# error in read TAG
			if code == tr.TAG_READ_ERROR:
				#print('Loop')
				if automaticTestEnable and automaticTest():
					code = tr.TAG_OK_ADD
					msg = "Automatic TAG"
				else:
					continue

			# Tag OK and increment
			if code == tr.TAG_OK_ADD:
				gui.makeBeep()
				gui.setCounts(gui.getCounts() + 1)
				gui.updateScreen()
				continue

			# Tag OK; No increment
			if code == tr.TAG_OK:
				gui.makeBeep()
				gui.updateScreen()
				continue

			# Favorite
			if code == tr.TAG_FAVORITE:
				gui.makeBeep()
				gui.setPopup(msg, 0)
				continue

			# Reference
			if code == tr.TAG_REFERENCE:
				gui.makeBeep()
				gui.setPopup(msg, 0)
				continue

			# Shutdown
			if code == tr.TAG_SHUTDOWN:
				gui.makeBeep()
				break

			# Update
			if code == tr.TAG_UPDATE:
				gui.makeBeep()
				connectWifi([(update, None)])
				continue

			# Upload
			if code == tr.TAG_UPLOAD:
				gui.makeBeep()
				connectWifi([(upload, None)])
				continue

			# Upload global
			if code == tr.TAG_UPLOAD_GLOBAL:
				gui.makeBeep()
				connectWifi([(uploadGlobal, None)])
				continue

			# Change ID and Name
			if code == tr.TAG_CHANGE_ID_NAME:
				gui.makeBeep()
				changeIdName(msg)
				continue

			# Change BeamianId
			if code == tr.TAG_BEAMIAN_ID:
				gui.makeBeep()
				changeBeamianId(msg)
				continue

			# Connect wifi
			if code == tr.TAG_WIFI_CONNECT:
				gui.makeBeep()
				connectWifi(None, True)
				continue

			# Disconnect wifi
			if code == tr.TAG_WIFI_DISCONNECT:
				gui.makeBeep()
				disconnectWifi()
				continue

			# Connect LoRa
			if code == tr.TAG_LORA_CONNECT:
				gui.makeBeep()
				connectLora(None)
				continue

			# Disconnect LoRa
			if code == tr.TAG_LORA_DISCONNECT:
				gui.makeBeep()
				disconnectLora()
				continue

			# Start Automatic Test
			if code == tr.TAG_AUTO_TEST_START:
				gui.makeBeep()
				automaticTestEnable = True
				continue

			# Stop Automatic Test
			if code == tr.TAG_AUTO_TEST_STOP:
				gui.makeBeep()
				automaticTestEnable = False
				connectWifi([(upload, None)])
				continue

			# Clear all tags
			if code == tr.TAG_CLEAR_ALL_TAGS:
				gui.makeBeep()
				gui.setPopup(msg, 2)
				lg.clearLogs()
				lora.clearLogs()
				gui.setCounts(lg.numberLogsInFile())
				continue

			# Clear LoRa tags
			if code == tr.TAG_CLEAR_LORA_TAGS:
				gui.makeBeep()
				gui.setPopup(msg, 2)
				lora.clearLogs()
				gui.setCounts(lg.numberLogsInFile())
				continue

			# Clear Wifi tags
			if code == tr.TAG_CLEAR_WIFI_TAGS:
				gui.makeBeep()
				lg.clearLogs()
				gui.setCounts(lg.numberLogsInFile())
				continue

			# Clear Wifi tags
			if code == tr.TAG_CLEAR_GLOBAL:
				gui.makeBeep()
				lg.removeGlobalLog()
				gui.setPopup(msg, 2)
				continue

			# Status (show status)
			if code == tr.TAG_STATUS:
				gui.makeBeep()
				gui.setPopup(msg + '\n' + 'Version ' + versionid + '\nIp Address: ' + wifi.getIp(), 4)

				# Error in TAG
			if code == tr.TAG_ERROR:
				gui.makeBeep()
				print(msg)
				gui.setPopup(msg, 2)
			
		gui.stop()
		tr.close()
		exitShutdown()

	except KeyboardInterrupt:
		print "Keyboard Interrupt"
		exitShutdown()

def executeShellCommand():
	print 'Executing apt-get purge ntp'
	out_cmd = "apt-get -y purge ntp"

	try:
		a = subprocess.check_output(out_cmd, shell=True, stderr=subprocess.STDOUT)
		print 'Finished purge ntp'

	except subprocess.CalledProcessError as cpe:
			print cpe.output

###################################################################################
# NAME        : automaticTest
# DESCRIPTION : Automatic Test
###################################################################################
_automaticTest_period = 4
_automaticTest_time = 0
_automaticTest_tagId_i = 0
def automaticTest():
	global tr, up, gui
	global _automaticTest_time, _automaticTest_tagId_i, _automaticTest_period

	actualTime = time.time()
	if actualTime - _automaticTest_time > _automaticTest_period:
		_automaticTest_time = actualTime
		#_automaticTest_period = randint(4, 29)
		tagId = up.getBeamianId() + 'E' + '{:04d}'.format(_automaticTest_tagId_i)
		_automaticTest_tagId_i += 1
		tr.logTag(tagId)
		gui.setPopup('Automatic TAG\n' + tagId, 2)
		sleep(1)
		return True

	return False

###################################################################################
# NAME        : ToastMsgPeriodic
# DESCRIPTION : Send a message on screen
###################################################################################
_toast_period = 10
_toast_time = 0
_toast_msg = ""
_toast_periodic_enable = False
def ToastMsgPeriodic(msg = None, enable = None):
	global up, gui
	global _toast_time, _toast_period, _toast_msg
	
	if(enable is not None):
		_toast_periodic_enable = enable

	if(_toast_periodic_enable == True):
		actualTime = time.time()
		if actualTime - _toast_time > _toast_period:
			_toast_time = actualTime
			if(msg is not None):
				_toast_msg = msg
			
			gui.setPopup(msg, 10)
			gui.makeBeep()
			return True

	return False

###################################################################################
# NAME        : threadingConnectWifi
# DESCRIPTION : Tries to connect to the wifi. After that, can run a function
# INPUT       : nextFunction - Function to run after connection
###################################################################################
_wifiFirstTimeCompleted = False
def threadingConnectWifi(nextFunctions, symbol=True):
	global wifi, up, nbiotEnable

	if wifi.isConnecting():
		if gui.getWifiStatus() == 2:
			gui.setWifi(True, 3)
		elif gui.getWifiStatus() == 3:
			gui.setWifi(True, 4)
		else:
			gui.setWifi(True, 2)

		th = threading.Timer(0.5, threadingConnectWifi, [nextFunctions, symbol])
		th.daemon = True
		th.start()
		return False

	else:
		#No connection
		global _wifiFirstTimeCompleted
		_wifiFirstTimeCompleted = True
		if not wifi.isConnected():
			print(       'Wifi network is not available')
			gui.setPopup('Wifi network is not available', 2)
			gui.setWifi(symbol, -1)
			if nbiotEnable:
				nbiot.powerOnModem()

			return False
		else:
			gui.setPopup('Ip Address: ' + wifi.getIp(), 5)
			gui.setWifi(symbol, 3)
			print(       'Ip Address: ' + wifi.getIp())
			up.updateTime()
			if nbiotEnable:
				nbiot.powerOnModem()

			if nextFunctions is not None and len(nextFunctions) > 0:
				for function, args in nextFunctions:
					if args is None:
						r = function()
					else:
						r = function(args)
						if r != True:
							return False

			return True

###################################################################################
# NAME        : connectWifi
# DESCRIPTION : Creates a tread to connect to the wifi
# INPUT       : nextFunction - Function to run after connection
#             : force        - Force a new connection
###################################################################################
def connectWifi(nextFunction=None, force=False, symbol=False):

	global wifi

	if force:
		wifi = Wifi(False)

	#Try connect
	if not wifi.isConnected():
		wifi.connect()

	threadingConnectWifi(nextFunction, symbol)

	return True

###################################################################################
# NAME        : keepWifi
# DESCRIPTION : Creates a tread to keep the wifi connection
###################################################################################
def keepWifi():
	th1 = threading.Timer(1, _keepWifiThread)
	th1.daemon = True
	th1.start()
	return

def _keepWifiThread():
	global wifi, _wifiFirstTimeCompleted
	wifiDisconnected = True

	while not _wifiFirstTimeCompleted:
		time.sleep(1)

	while True:
		if wifi.isConnected():
			ip_gateway = wifi.getIpGateway()
			print("ip gateway wifi: %s" % ip_gateway)

			if wifi.ping(ip_gateway):
				#print('Ok, do nothing')
				wifiDisconnected = False

		if wifiDisconnected:
			wifi.connect(True)

			if not wifi.isConnected():
				print('Wifi network is not available')
				gui.setWifi(True, -1)

			else:
				gui.setPopup('Ip Address: ' + wifi.getIp(), 5)
				gui.setWifi(True, 3)
				print(       'Ip Address: ' + wifi.getIp())
				up.updateTime()
		time.sleep(60)

	return True

###################################################################################
# NAME        : disconnectWifi
# DESCRIPTION : Disconnect wifi interface
###################################################################################
def disconnectWifi():

	global wifi, gui

	wifi.disconnect()
	gui.setWifi(False)

	return True


###################################################################################
# NAME        : connectNbiot
# DESCRIPTION : Creates a tread to connect to the NBiot
# INPUT       : nextFunction - Function to run after connection
#             : force        - Force a new connection
###################################################################################
def connectNbiot(nextFunction=None, force=False, symbol=False):

	global nbiot

	if force:
		nbiot = NBiot(False)

	#Try connect
	print('try connect nbiot')
	if (not nbiot.isConnected()) and nbiot.modemIsPowered():
		print('call connect nbiot')
		nbiot.connect()
		threadingConnectNbiot(nextFunction, symbol)
		return True
	
	return False


###################################################################################
# NAME        : threadingConnectNBiot
# DESCRIPTION : Tries to connect to the nbiot. After that, can run a function
# INPUT       : nextFunction - Function to run after connection
###################################################################################
_nbiotFirstTimeCompleted = False
def threadingConnectNbiot(nextFunctions, symbol=True):
	global nbiot, up

	if nbiot.isConnecting():
		gui.setLora(True, 0) # reuse lora

		th = threading.Timer(0.5, threadingConnectNbiot, [nextFunctions, symbol])
		th.daemon = True
		th.start()
		return False

	else:
		#No connection
		global _nbiotFirstTimeCompleted
		_nbiotFirstTimeCompleted = True
		if not nbiot.isConnected():
			print(       'NBiot is not available')
			gui.setPopup('NBiot is not available', 2)
			gui.setLora(True, -1) # reuse lora
			return False
		else:
			gui.setPopup('Ip Address: ' + nbiot.getIp(), 5)
			gui.setLora(True, 1) # reuse lora
			print(       'Ip Address: ' + nbiot.getIp())
			#up.updateTime()

			if nextFunctions is not None and len(nextFunctions) > 0:
				for function, args in nextFunctions:
					if args is None:
						r = function()
					else:
						r = function(args)
						if r != True:
							return False

			return True

###################################################################################
# NAME        : disconnectNbiot
# DESCRIPTION : Disconnect nbiit interface
###################################################################################
def disconnectNbiot():

	global nbiot, gui

	nbiot.disconnect()
	gui.setLora(False)

	return True

###################################################################################
# NAME        : connectLora
# DESCRIPTION : Connect to the LoRa network
# INPUT       : nextFunction - Function to run after connection
###################################################################################
def connectLora(nextFunction=None):

	global lora, gui,tr, loraEnable

	if (lora.setup() == True):
		tr.setLoraLogFunc(lora.writeLineLora)
		gui.setLora(True, -1)
		loraEnable = True
		lora.runLoraThreads()

		return True

	return False

###################################################################################
# NAME        : disconnectLora
# DESCRIPTION : Disconnect lora interface
###################################################################################
def disconnectLora():

	global lora, gui,tr, loraEnable
	#lora.reset()
	tr.setLoraLogFunc(None)
	lora.stopLoraThreads()
	loraEnable = False
	gui.setLora(False)

###################################################################################
# NAME        : updateGuiLora
# DESCRIPTION : Disconnect lora interface
###################################################################################
def updateGuiLora():

	global lora, gui
	
	if lora.isLoraConnected():
		gui.setLora(True, 1)
	else:
		gui.setLora(True, 0)

###################################################################################
# NAME        : checkLoraThreadStatus
# DESCRIPTION : check if lora thread is blocked
###################################################################################
def checkLoraThreadStatus():

	global lora

	if lora.getResetCount() > 60:
		ToastMsgPeriodic("Please\nReset\nDevice", True)
		return

		
	threadTimestamp = lora.getLoraTimestamp()
	if threadTimestamp + timedelta(minutes=5) < datetime.now():
		print "lora thread blocked, restarting app"
		time.sleep(1)
		print "restarting..."
		exitRestartApp()
		#disconnectLora()
		#print "disconnect lora"
		#connectLora(None)
		#print "connect lora"
		#return True
	#return False





###################################################################################
# NAME        : AutoBoot
# DESCRIPTION : Automatic routines after boot
###################################################################################
def autoBoot(force, wifiPresistent):
	global gui, lg, up, lora

	conf = Config()
	c_boot = conf.getBoot()

	functions = []

	# First operation: Update
	functions.append((update, None))

	try:
		if c_boot is not None and c_boot['enableUpload']:
			#print('Upload logs')
			functions.append((upload, False))

		if c_boot is not None and c_boot['enableDeleteWifi']:
			#print('Delete Wifi Logs')
			functions.append((lg.clearLogs, None))

		if c_boot is not None and c_boot['enableDeleteLora']:
			#print('Delete Lora Logs')
			functions.append((lora.clearLogs, None))

	except:
		pass

	connectWifi(functions, force, wifiPresistent)

###################################################################################
# NAME        : update
# DESCRIPTION : Checks for firmware changes in server
###################################################################################
def update():
	global gui
	print(       'Checking for updates')
	gui.setPopup('Checking for updates')
	sleep(1)
	version, data, msg = up.checkUpdates()
	gui.setPopup(msg, 2)

	if data !=  None:
		print msg
		v, m = up.updateData()
		gui.setPopup(m + '\n\n' + data, 2)
		if v:
			gui.stop()
			exitRestartApp()

	if version !=  None:
		print msg
		v, m = up.updateFirmware()
		gui.setPopup(m + '\n\n' + version, 2)
		sleep(1)

		#valid image downloaded
		if v:
			gui.stop()
			exitUpdate()

	if version == None and data == None:
		sleep(0.1)

	return True

###################################################################################
# NAME        : upload
# DESCRIPTION : Send the log file to the FTP server
###################################################################################
def upload(delete=True):
	global gui, lg, up

	print(       'Uploading log file')
	gui.setPopup('Uploading log file', 0)

	try:
		up.sendConfig()
	except Exception, e:
		print(str(e))
	r, m = lg.sendLog(delete)

	print(m)
	gui.setCounts(lg.numberLogsInFile())
	gui.setPopup(m, 2)

	return r

###################################################################################
# NAME        : uploadGlobal
# DESCRIPTION : Send the global log file to the FTP server
###################################################################################
def uploadGlobal(delete=False):
	global gui, lg, up

	print(       'Uploading global log file')
	gui.setPopup('Uploading global log', 0)

	try:
		up.sendConfig()
	except Exception, e:
		print(str(e))
	r, m = lg.sendGlobalLog(delete)

	print(m)
	gui.setPopup(m, 2)

	return r

###################################################################################
# NAME        : changeBeamianId
# DESCRIPTION : Change the Beamian ID, including the hostname (in network).
#             : Needs to restart
###################################################################################
def changeBeamianId(msg):
	global gui, up
	up.newBeamianId(msg)

	print(       'New Beamian ID: \n' + msg)
	gui.setPopup('New Beamian ID: \n' + msg, 2)

	gui.stop()
	exitRestart()

###################################################################################
# NAME        : changeIdName
# DESCRIPTION : Change the displayed ID and Company Name. Need to restart app
# INPUT       : msg = [id,name]
###################################################################################
def changeIdName(msg):
	global gui, up

	#default
	if msg == None or msg == '':
		up.setId(None)
		up.setCompanyName(None)
	else:
		msg = msg.split(',')

		if len(msg) >= 2:
			up.setId(msg[0])
			up.setCompanyName(msg[1])
		else:
			up.setId(msg[0])
			up.setCompanyName(None)

	print(       'New ID:\n' + up.getId() + '\n' + up.getCompanyName())
	gui.setPopup('New ID:\n' + up.getId() + '\n' + up.getCompanyName(), 2)
	sleep(2)

	gui.stop()
	exitRestartApp()

####################################
# Exit codes
####################################
import os
def exitRestart():
	global tr;
	tr.close()
	os._exit(1)

def exitShutdown():
	global tr;
	tr.close()
	os._exit(2)

def exitUpdate():
	global tr;
	tr.close()
	os._exit(3)

def exitRestartApp():
	global tr;
	tr.close()
	os._exit(4)

####################################
# Check first time
####################################
def firstRun():
	#verify if file exist
	return not (os.path.isfile('.loaded.txt'))

def firstRunSuccess():
	#create loaded file
	return open('.loaded.txt', 'w+').close()

####################################
# System entry point
####################################
if __name__ == "__main__":
	main()
