#!/usr/bin/python
'''
Lists the various available experiments and allows users to run them
'''

from __future__ import print_function
from argparse import ArgumentParser
import sys,time

parser = ArgumentParser()
# Add more options if you like
parser.add_argument("-P", dest="PortName", help="If you have connected multiple devices, provide the port name . e.g /dev/ttyACM0", metavar="PORT_NAME")
args = parser.parse_args()

import os
os.environ['QT_API'] = 'pyqt'
import sip
sip.setapi("QString", 2)
sip.setapi("QVariant", 2)
from PyQt4 import QtCore, QtGui
from PyQt4 import QtWebKit

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

from SEEL_Apps.stylesheets import styles
from SEEL_Apps.utilitiesClass import utilitiesClass
from SEEL_Apps.templates import single_col_exp

import os,string,time,pkgutil,importlib,functools,pkg_resources,serial.tools.list_ports

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

class MyMainWindow(QtGui.QMainWindow, single_col_exp.Ui_MainWindow,utilitiesClass):
	def __init__(self, parent=None,**kwargs):
		super(MyMainWindow, self).__init__(parent)
		self.eventHandler = kwargs.get('app',None)
		self.showSplash();self.updateSplash(10,'Setting up UI...')


		self.setupUi(self)
		self.styleText = pkg_resources.resource_string('SEEL_Apps', "stylesheets/overall.css").decode("utf-8")
		self.setStyleSheet(self.styleText)
		self.updateSplash(10,'Fetching libraries...')

		from SEEL import interface
		self.row=0;	self.col=0;	self.colLimit=3

		self.updateSplash(10,'Connecting to Device...')
		if  args.PortName: self.I = interface.Interface(port = args.PortName)  #Not using connect method because it returns None if unconnected,
		else: self.I = interface.Interface(verbose=False)                       #and I need to pass the methods of interface to various control widgets regardless
		self.hexid=''
		try:
			if not self.I.connected:
				if len(self.I.H.occupiedPorts):
					diag = QtGui.QMessageBox.about(self,'Error','Could not find available device.\nSoftware already running for ports: %s'%self.I.H.occupiedPorts)
				else:
					diag = QtGui.QMessageBox.about(self,'Error','Could not find available device')
				#diag.show()
				self.styleText = pkg_resources.resource_string('SEEL_Apps', "stylesheets/overall_disconnected.css").decode("utf-8")
				self.setStyleSheet(self.styleText)
				self.setWindowTitle('Error : Not Connected')
			else:
				self.hexid = hex(self.I.device_id()&0xFFFF)
				self.setWindowTitle(self.I.generic_name + ' : '+self.I.H.version_string.decode("utf-8")+' - '+self.hexid)
		except:
			self.updateSplash(30,'Connection Error!')
			#self.SCF1.setStyleSheet(_fromUtf8(styles.disconnected))
			self.styleText = pkg_resources.resource_string('SEEL_Apps', "stylesheets/overall_disconnected.css").decode("utf-8")
			self.setStyleSheet(self.styleText)
			pass

		#################################   POPULATE ADVANCED CONTROLS TAB  ##################################
		self.ExperimentLayout.setAlignment(QtCore.Qt.AlignTop|QtCore.Qt.AlignLeft)
		self.WidgetLayout.setAlignment(QtCore.Qt.AlignTop|QtCore.Qt.AlignLeft)
		self.advancedControlsLayout.setAlignment(QtCore.Qt.AlignTop|QtCore.Qt.AlignLeft)
		#Widgets related to power supplies PV1,PVS2,PV3,PCS
		self.supplySection = self.supplyWidget(self.I);		self.advancedControlsLayout.addWidget(self.supplySection)
		#Widgets related to Analog Waveform generators
		self.sineSection = self.sineWidget(self.I); 		self.advancedControlsLayout.addWidget(self.sineSection)
		#Widgets related to Digital Waveform generators
		self.pwmSection = self.pwmWidget(self.I);   		self.advancedControlsLayout.addWidget(self.pwmSection)
		self.advancedControlsLayout.addWidget(self.setStateIcon(I=self.I))


		#################################   POPULATE EXPERIMENTS TAB  ########################################
		sys.path.append('/usr/share/seelablet')
		self.tmpTimer = QtCore.QTimer()
		self.tmpTimer.singleShot(2,functools.partial(self.loadParentModuleIcons,'seel_res.GUI'))
		

		#################################   POPULATE CONTROLS TAB  ##########################################
		row=0;col=0;colLimit=3
		self.funcs=[]
		autogenControls=[]
		autogenControls.append({'TITLE':'Wave 1','MIN':1,'MAX':5000,'FUNC':self.I.set_w1,'TYPE':'dial','UNITS':'Hz','TOOLTIP':'Frequency of waveform generator #1','LINK':self.updateWAVE1_FREQ})
		autogenControls.append({'TITLE':'Wave 2','MIN':1,'MAX':5000,'FUNC':self.I.set_w2,'TYPE':'dial','UNITS':'Hz','TOOLTIP':'Frequency of waveform generator #2','LINK':self.updateWAVE2_FREQ})
		autogenControls.append({'TITLE':'square 1','MIN':0,'MAX':50000,'FUNC':self.modifiedSqr1,'TYPE':'dial','UNITS':'Hz','TOOLTIP':'Frequency of square wave generator #1\n0 for switched off, Max for On state'})

		tmpfunc = functools.partial(self.I.DAC.__setRawVoltage__,'PV1')
		autogenControls.append({'TITLE':'PV1','MIN':0,'MAX':4095,'FUNC':tmpfunc,'TYPE':'dial','UNITS':'V','TOOLTIP':'Programmable Voltage Source ','LINK':self.updatePV1_LABEL})

		tmpfunc = functools.partial(self.I.DAC.__setRawVoltage__,'PV2')
		autogenControls.append({'TITLE':'PV2','MIN':0,'MAX':4095,'FUNC':tmpfunc,'TYPE':'dial','UNITS':'V','TOOLTIP':'Programmable Voltage Source ','LINK':self.updatePV2_LABEL})

		tmpfunc = functools.partial(self.I.DAC.__setRawVoltage__,'PV3')
		autogenControls.append({'TITLE':'PV3','MIN':0,'MAX':4095,'FUNC':tmpfunc,'TYPE':'dial','UNITS':'V','TOOLTIP':'Programmable Voltage Source ','LINK':self.updatePV3_LABEL})

		tmpfunc = lambda x: self.I.DAC.__setRawVoltage__('PCS',4095-x)
		autogenControls.append({'TITLE':'PCS','MIN':0,'MAX':4095,'FUNC':tmpfunc,'TYPE':'dial','UNITS':'mA','TOOLTIP':'Programmable Current Source ','SCALE_FACTOR' : 1e3,'LINK':self.updatePCS_LABEL})

		autogenControls.append({'TYPE':'separator'})

		autogenControls.append({'TITLE':'CAPACITANCE','FUNC':self.I.get_capacitance,'TYPE':'button','UNITS':'F','TOOLTIP':'Read Capacitance connected to CAP input '})
		tmpfunc = functools.partial(self.I.get_average_voltage,samples=100)
		autogenControls.append({'TITLE':'VOLTMETER','FUNC':tmpfunc,'TYPE':'selectButton','UNITS':'V','TOOLTIP':'Voltmeter','OPTIONS':self.I.allAnalogChannels})
		autogenControls.append({'TITLE':'Low Frequency','FUNC':self.I.get_freq,'TYPE':'selectButton','UNITS':'Hz','TOOLTIP':'Measure Frequency. Minimum 40Hz','OPTIONS':self.I.allDigitalChannels})
		autogenControls.append({'TITLE':'High Frequency','FUNC':self.I.get_high_freq,'TYPE':'selectButton','UNITS':'Hz','TOOLTIP':'Measure Frequencies over 1MHz with 10Hz resolution','OPTIONS':self.I.allDigitalChannels})
		autogenControls.append({'TITLE':'SR-04 Distance','FUNC':self.I.estimateDistance,'TYPE':'button','UNITS':'m','TOOLTIP':'Measure Distance using an HCSR04 sensor. TRIG-SQR1  , ECHO-ID1'})
		autogenControls.append({'TYPE':'pulsecounter'})


		for C in autogenControls:
			if C['TYPE']=='dial':
				self.funcs.append(C.get('FUNC',None))
				self.WidgetLayout.addWidget(self.dialIcon(**C),row,col)
			elif C['TYPE']=='button':
				self.funcs.append(C.get('FUNC',None))
				self.WidgetLayout.addWidget(self.buttonIcon(**C),row,col)
			elif C['TYPE']=='selectButton':
				self.funcs.append(C.get('FUNC',None))
				self.WidgetLayout.addWidget(self.selectAndButtonIcon(**C),row,col)
			elif C['TYPE']=='pulsecounter':
				self.WidgetLayout.addWidget(self.pulseCounterIcon(self.I),row,col)
			elif C['TYPE'] == 'separator':
				row+=1;col=0
				line = QtGui.QFrame();	line.setFrameShape(QtGui.QFrame.HLine);	line.setFrameShadow(QtGui.QFrame.Sunken); self.WidgetLayout.addWidget(line,row,0,1,colLimit)
				row+=1;col=-1

			col+=1
			if(col==colLimit):
				col=0;row+=1

		col+=1
		if(col==colLimit):
				col=0;row+=1

		self.menu_entries=[]
		self.menu_group=None

		self.helpView = QtWebKit.QWebView()
		self.helpLayout.addWidget(self.helpView)

		self.shortlist=[]
		self.timer = QtCore.QTimer()
		self.timer.timeout.connect(self.locateDevices)
		self.timer.start(500)
		self.updateSplash(30,'Almost done...')
		#self.splash.finish(self)
		self.runningApp = None

	def modifiedSqr1(self,val):
		if val<4:
			self.I.set_state(SQR1=0)
			return 'LOW'
		elif val==50000:
			self.I.set_state(SQR1=1)
			return 'HIGH'
		else:
			v = self.I.sqr1(val)
			return v

	def loadList(self,basepackage):
		self.funcs=[]
		baselib = importlib.import_module(basepackage)
		apps = [name for _, name, _ in pkgutil.iter_modules([os.path.dirname(baselib.__file__)])]
		print(apps)
		self.colLimit=1
		for app in apps:
			if(self.col==self.colLimit):
				self.col=0;self.row+=1
			fn = functools.partial(self.launchFunc,basepackage+'.'+app)
			self.funcs.append(fn)
			icon = self.experimentListItem(basepackage,app,fn)
			self.ExperimentLayout.addWidget(icon,self.row,self.col)
			icon.mouseHover.connect(self.setHint)
			self.col+=1

		self.col=0;self.row+=1
		line = QtGui.QFrame()
		line.setFrameShape(QtGui.QFrame.HLine)
		line.setFrameShadow(QtGui.QFrame.Sunken)
		self.ExperimentLayout.addWidget(line,self.row,0,1,3)
		self.row+=1
		
	def importExperimentFolder(self):
		from os.path import expanduser
		dirname = QtGui.QFileDialog.getExistingDirectory(self,  "Load a folder containing Experiments", expanduser("./"),  QtGui.QFileDialog.ShowDirsOnly)
		if not dirname:return
		parentPath = os.path.abspath(os.path.join(dirname, os.pardir))
		sys.path.append(parentPath)
		self.loadSubmoduleIcons(dirname.split('/')[-1])
		self.tmpTimer.singleShot(2,self.scrollToEnd)
		
	def scrollToEnd(self):
		scrollMax = self.ExperimentScrollArea.verticalScrollBar().maximum()
		self.ExperimentScrollArea.verticalScrollBar().setValue(scrollMax)

	'''
	def importExperimentFile(self):
		from os.path import expanduser
		filename = QtGui.QFileDialog.getOpenFileName(self,  "Load an app", expanduser("./"), 'PY(*.py)')
		if not filename:return
		parentPath = os.path.abspath(os.path.join(filename, os.pardir))
		parentPath2 = os.path.abspath(os.path.join(parentPath, os.pardir))
		sys.path.append(parentPath2)
		filename = filename.split('/')[-1]
		print(filename,parentPath2)
		
		self.col=0;self.row+=1
		modname = filename.split('.')[0]
		fn = functools.partial(self.launchFunc,parentPath.split('/')[-1]+'.'+modname)
		self.funcs.append(fn)
		icon = self.experimentIcon(parentPath.split('/')[-1],modname,fn)
		self.ExperimentLayout.addWidget(icon,self.row,self.col)
		icon.mouseHover.connect(self.setHint)
		self.col+=1
	'''
	
	def loadParentModuleIcons(self,basepackage):
		self.funcs=[]
		baselib = importlib.import_module(basepackage)
		subdirs = [name for _, name,isDir in pkgutil.iter_modules([os.path.dirname(baselib.__file__)]) if isDir==True]
		print (subdirs)
		for sub in subdirs:
			submodule = basepackage+'.'+sub
			self.loadSubmoduleIcons(submodule)

	def loadSubmoduleIcons(self,submodule):
			sublib = importlib.import_module(submodule)
			title = QtGui.QLabel();	title.setText(sublib.__dict__.get('title',submodule.split('.')[-1][1:] )); self.ExperimentLayout.addWidget(title,self.row,0,1,self.colLimit)
			self.col=0;self.row+=1
			line = QtGui.QFrame();	line.setFrameShape(QtGui.QFrame.HLine);	line.setFrameShadow(QtGui.QFrame.Sunken); self.ExperimentLayout.addWidget(line,self.row,0,1,self.colLimit)
			self.col=0;self.row+=1
			apps = [name for _, name,isDir in pkgutil.iter_modules([os.path.dirname(sublib.__file__)]) if isDir==False]
			for app in apps:
				if(self.col==self.colLimit):
					self.col=0;self.row+=1

				appLocation = submodule+'.'+app
				fname = app+'.'+'.html'


				#Find a universally available helpfile
				helpurl = pkg_resources.resource_filename('seel_res.HTML',app+'.html')
				if not os.path.isfile(helpurl): helpurl=None

				#Find a locally available helpfile, and use it if it exists
				try:
					localhelpurl = pkg_resources.resource_filename(submodule+'.'+'HTML',app+'.html')
					if os.path.isfile(localhelpurl): helpurl=localhelpurl
				except:
					pass

				fn = functools.partial(self.launchFunc,appLocation,helpurl)
				self.funcs.append(fn)
				icon = self.experimentIcon(submodule,app,fn)
				self.ExperimentLayout.addWidget(icon,self.row,self.col)
				icon.mouseHover.connect(self.setHint)
				self.col+=1

			self.col=0;self.row+=1

	def clearExperimentIcons(self):
		while self.ExperimentLayout.count():
			item = self.ExperimentLayout.takeAt(0)
			item.widget().deleteLater()
		self.row=0;
		self.col=0;
		self.colLimit=3

	def setHint(self,t):
		self.hintText.setHtml(t)

	def launchFunc(self,fname,helpurl):
		if self.I:
			#try:
			#	if fname.split('.')[1]=='apps':
			if self.runningApp:
				self.runningApp.close()
			#except: pass
			FILE = importlib.import_module(fname)
			inst = FILE.AppWindow(self,I=self.I)
			inst.show()
			size = inst.geometry()
			inst.setGeometry(400, 50,size.width(), size.height())

			self.runningApp = inst
			#Load help HTML if found
			self.helpView.setUrl(QtCore.QUrl(helpurl))          
			self.tabWidget.setCurrentIndex(1)

		else:
			print(self.setWindowTitle('Device Not Connected!'))

	def returnToApps(self):
		self.tabWidget.setCurrentIndex(0)
		
	def launchAboutDevice(self):
		print ('about')
		if self.I:
			from SEEL_Apps.utilityApps import deviceInfo
			info = deviceInfo.AppWindow(self,I=self.I)
			info.show()
		else:
			print (self.setWindowTitle('Device Not Connected!'))

	def locateDevices(self):
		L = serial.tools.list_ports.comports()
		shortlist=[]
		for a in L:
			if ('ACM' in a[1]) and ('04d8:00df' in a[2]):
				shortlist.append(a)

		total = len(shortlist)
		if shortlist != self.shortlist:
			self.shortlist=shortlist
			for a in self.menu_entries:
				self.deviceCombo.removeItem(0)
			self.menu_entries=[]
			for a in shortlist:
				self.deviceCombo.addItem(a[0])
				self.menu_entries.append(a[0])


	def selectDevice(self):
		sel = self.deviceCombo.currentText()
		if ( not ('ACM' in sel  or 'USB' in sel)):
			self.displayDialog('No devices Found')
			return
		self.splash.show();self.progressBar.setValue(0)
		self.updateSplash(20,'Reconnecting to %s'%(sel));
		self.updateSplash(20,'Reconnecting to %s'%(sel));
		if self.I:
			self.I.reconnect(port = sel)
			self.updateSplash(20,'Finished Reconnecting...');
			self.displayDialog('Reconnected :'+self.I.H.version_string.decode("utf-8"))
			#self.SCF1.setStyleSheet(_fromUtf8(styles.connected))
			self.styleText = pkg_resources.resource_string('SEEL_Apps', "stylesheets/overall.css").decode("utf-8")
			self.setStyleSheet(self.styleText)
			self.setWindowTitle('SEELablet : '+self.I.H.version_string.decode("utf-8"))
		else:
			self.I = interface.connect(port = sel)
			self.updateSplash(20,'Finished Loading...');
			self.displayDialog('Connected :'+self.I.H.version_string.decode("utf-8"))
			try:
				#self.SCF1.setStyleSheet(_fromUtf8(styles.connected))
				self.styleText = pkg_resources.resource_string('SEEL_Apps', "stylesheets/overall.css").decode("utf-8")
				self.setStyleSheet(self.styleText)
				self.setWindowTitle('SEELablet : '+self.I.H.version_string.decode("utf-8"))
			except:
				pass
		self.splash.close()
		return

	def updateSplash(self,x,txt=''):
		self.progressBar.setValue(self.progressBar.value()+x)
		if(len(txt)):self.splashMsg.setText('  '+txt)
		self.eventHandler.processEvents()
		self.splash.repaint()

	def showSplash(self):
		splash_pix = QtGui.QPixmap(pkg_resources.resource_filename('SEEL_Apps.stylesheets', "splash.png"))
		self.splash = QtGui.QSplashScreen(splash_pix)# QtCore.Qt.WindowStaysOnTopHint)
		# adding progress bar
		self.progressBar = QtGui.QProgressBar(self.splash); self.progressBar.move(0,self.splash.height()-20)
		self.splashMsg = QtGui.QLabel(self.splash);self.splashMsg.setStyleSheet("font-weight:bold;color:white")
		self.progressBar.resize(self.splash.width(),20)
		self.splashMsg.setText('Loading....');self.splashMsg.resize(self.progressBar.width(),20) ; self.splashMsg.move(0,self.splash.height()-20)
		css = pkg_resources.resource_string('SEEL_Apps', "stylesheets/splash.css").decode("utf-8")
		if css:
			self.splash.setStyleSheet(css)
		self.splash.setMask(splash_pix.mask())
		self.splash.show()


	def resetDevice(self):
		if self.I:
			if self.I.connected:
				self.I.resetHardware()
				self.I.H.fd.close()
				self.I.reconnect()

	def __del__(self):
		try:
			self.I.H.fd.close()
		except:
			pass
		print('bye')

	############################Section for correlating control widgets#################################

	def updateWAVE1_FREQ(self,value,units=''):
		self.sineSection.WAVE1_FREQ.setText('%.3f %s '%(value,units))
	def updateWAVE2_FREQ(self,value,units=''):
		self.sineSection.WAVE2_FREQ.setText('%.3f %s '%(value,units))
	def updatePV1_LABEL(self,value,units=''):
		self.supplySection.PV1_LABEL.setText('%.3f %s '%(value,units))
	def updatePV2_LABEL(self,value,units=''):
		self.supplySection.PV2_LABEL.setText('%.3f %s '%(value,units))
	def updatePV3_LABEL(self,value,units=''):
		self.supplySection.PV3_LABEL.setText('%.3f %s '%(value,units))
	def updatePCS_LABEL(self,value,units=''):
		self.supplySection.PCS_LABEL.setText('%.3f %s '%(value,units))

	def measure_dcycle(self):
		inp = self.timing_input.currentText()
		v=self.I.DutyCycle(inp)
		if(v[0]!=-1):p=100*v[1]
		else: p=0
		self.timing_results.setText('Duty Cycle: %f %%'%(p))

	def measure_interval(self):
		t = self.I.MeasureInterval(self.edge1chan.currentText(),self.edge2chan.currentText(),self.edge1edge.currentText(),self.edge2edge.currentText())
		self.time_interval_label.setText('time: %.2e S'%(t))

	def startRemoteServer(self):
		try:
			from SEEL_Apps.utilityApps import remote
			import inspect

			funcs=dir(self.I)
			self.methods={}
			self.function_list=[]
			for a in funcs:
				fn=getattr(self.I,a)
				try:
					args=inspect.getargspec(fn).args
				except:
					args=[]

				if len(args)>0:
					if inspect.ismethod(fn):
						self.methods[a]=(fn,args)		#list of tuples of all methods in device handler
						if args[0]=='self': self.function_list.append([a,args[1:] ])

			print (self.function_list)
			print (self.methods)


			self.remote = remote.CherryPyClass(self.function_list,self.methods)
			self.remoteThread = remote.CherryPyThread(self.remote)
			#thread.finished.connect(app.exit)
			self.remoteThread.start()
			QtGui.QMessageBox.about(self,'Server Ready','''Try accessing <span style="font-weight:bold;">localhost:8080/get_voltage('CH1')</span> from your web browser.<br> Do not access the device from multiple locations simultaneously.''')
			
		except:
			QtGui.QMessageBox.about(self,'Unable to Launch Server',"Is CherryPy web server installed?")



if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    myapp = MyMainWindow(app=app)
    myapp.show()
    myapp.splash.finish(myapp)
    sys.exit(app.exec_())
