#!/usr/bin/env python3
# -*- coding: utf-8 -*- 

import os 
import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.websocket
import alsaaudio
import threading
import time
import numpy 
import gc
from opus.decoder import Decoder as OpusDecoder
import datetime
import configparser
import sys
import Hamlib
from rtlsdr import RtlSdr
import numpy as np
import math

############ Global variables ##################################
CTRX=None
config = configparser.ConfigParser()
config.read('UHRR.conf')
e="No"

############ Global functions ##################################
def writte_log(logmsg):
	logfile = open(config['SERVER']['log_file'],"w") 
	msg = str(datetime.datetime.now())+":"+str(logmsg)
	logfile.write(msg) 
	print(msg)
	logfile.close()

############ BaseHandler tornado ##############
class BaseHandler(tornado.web.RequestHandler):
	def get_current_user(self):
		return self.get_secure_cookie("user")

############ Generate and send FFT from RTLSDR ##############
is_rtlsdr_present = True

try:
	FFTSIZE=4096
	nbBuffer=24
	nbsamples=nbBuffer/2*FFTSIZE
	ptime=nbsamples/int(config['PANADAPTER']['sample_rate'])
	sdr_windows = eval("np."+config['PANADAPTER']['fft_window']+ "(FFTSIZE)") 
	fftpaquetlen=int(FFTSIZE*8/2048)
	sdr = RtlSdr()
	sdr.sample_rate = int(config['PANADAPTER']['sample_rate']) # Hz
	sdr.center_freq = int(config['PANADAPTER']['center_freq']) # Hz
	sdr.freq_correction = int(config['PANADAPTER']['freq_correction']) # PPM
	sdr.gain = int(config['PANADAPTER']['gain']) #or 'auto'
except:
	is_rtlsdr_present = False
	

	
AudioPanaHandlerClients = []

class loadFFTdata(threading.Thread):

	def __init__(self):
		threading.Thread.__init__(self)
		self.get_log_power_spectrum_w = np.empty(FFTSIZE)
		for i in range(FFTSIZE):
			self.get_log_power_spectrum_w[i] = 0.5 * (1. - math.cos((2 * math.pi * i) / (FFTSIZE - 1)))

	def run(self):
		while True:
			time.sleep(ptime)
			self.getFFT_data()

			
	def get_log_power_spectrum(self,data):

			pulse = 10
			rejected_count = 0
			power_spectrum = np.zeros(FFTSIZE)
			db_adjust = 20. * math.log10(FFTSIZE * 2 ** 15)

			# Time-domain analysis: Often we have long normal signals interrupted
			# by huge wide-band pulses that degrade our power spectrum average.
			# We find the "normal" signal level, by computing the median of the
			# absolute value.  We only do this for the first buffer of a chunk,
			# using the median for the remaining buffers in the chunk.
			# A "noise pulse" is a signal level greater than some threshold
			# times the median.  When such a pulse is found, we skip the current
			# buffer.  It would be better to blank out just the pulse, but that
			# would be more costly in CPU time.

			# Find the median abs value of first buffer to use for this chunk.
			td_median = np.median(np.abs(data[:FFTSIZE]))
			# Calculate our current threshold relative to measured median.
			td_threshold = pulse * td_median
			nbuf_taken = 0  # Actual number of buffers accumulated
			for ic in range(nbBuffer-1):
				start=ic * int(FFTSIZE/2)
				end=start+FFTSIZE
				td_segment = data[start:end]*sdr_windows

				# remove the 0hz spike
				td_segment = np.subtract(td_segment, np.average(td_segment))

				td_max = np.amax(np.abs(td_segment))  # Do we have a noise pulse?
				if td_max < td_threshold:  # No, get pwr spectrum etc.
					# EXPERIMENTAL TAPERfd
					td_segment *= self.get_log_power_spectrum_w

					fd_spectrum = np.fft.fft(td_segment)
					# Frequency-domain:
					# Rotate array to place 0 freq. in center.  (It was at left.)
					fd_spectrum_rot = np.fft.fftshift(fd_spectrum)
					# Compute the real-valued squared magnitude (ie power) and 
					# accumulate into pwr_acc.
					# fastest way to sum |z|**2 ??
					nbuf_taken += 1
					power_spectrum = power_spectrum + \
						np.real(fd_spectrum_rot * fd_spectrum_rot.conj())
				else:  # Yes, abort buffer.
					rejected_count += 1
					# if DEBUG: print "REJECT! %d" % self.rejected_count
			if nbuf_taken > 0:
				power_spectrum = power_spectrum / nbuf_taken  # normalize the sum.
			else:
				power_spectrum = np.ones(FFTSIZE)  # if no good buffers!
			# Convert to dB. Note log(0) = "-inf" in Numpy. It can happen if ADC 
			# isn't working right. Numpy issues a warning.
			log_power_spectrum = 10. * np.log10(power_spectrum)
			return log_power_spectrum - db_adjust  # max poss. signal = 0 dB

	def getFFT_data(self):
		samples = sdr.read_samples(nbsamples)
		samples = np.imag(samples) + 1j * np.real(samples)

		max_pow = -254
		min_pow = 0

		power = self.get_log_power_spectrum(samples)

		# search whole data set for maximum and minimum value
		for dat in power:
			if dat > max_pow:
				max_pow = dat
			elif dat < min_pow:
				min_pow = dat
				
		byteslist=bytearray()
		try:
			for dat in power:
				try:
					byteslist.append(self.FFTmymap(dat, min_pow, max_pow, 0, 255))
				except (RuntimeError, TypeError, NameError):
					byteslist.append(255)
					pass
			byteslist+=bytearray((65280+int(min_pow)).to_bytes(2, byteorder="big"))
			byteslist+=bytearray((65280+int(max_pow)).to_bytes(2, byteorder="big")) 
			for c in AudioPanaHandlerClients:
				c.fftframes.append(bytes(byteslist))
		except:
			return None
		
	def FFTmymap(self, x, in_min, in_max, out_min, out_max):
		ret=int((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min)
		return ret



class WS_panFFTHandler(tornado.websocket.WebSocketHandler):

	@tornado.gen.coroutine
	def sendFFT(self):
		global ptime, fftpaquetlen
		try:
			while len(self.fftframes)>0:
				yield self.write_message(self.fftframes[0],binary=True)
				del self.fftframes[0] 
		except:
			return None
		tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=ptime), self.sendFFT)

	def open(self):
		global is_rtlsdr_present
		print('new connection on FFT socket, is_rtlsdr_present = '+str(is_rtlsdr_present))
		if self not in AudioPanaHandlerClients:
			AudioPanaHandlerClients.append(self)
		self.fftframes = []
			
	def on_message(self, data) :
		print(data)
		if str(data)=="ready":
			self.sendFFT()
		elif str(data)=="init":
			self.write_message("fftsr:"+str(config['PANADAPTER']['sample_rate']));
			self.write_message("fftsz:"+str(FFTSIZE));
			self.write_message("fftst");

	def on_close(self):
		print('connection closed for FFT socket')

############ websocket for send RX audio from TRX ##############
flagWavstart = False
AudioRXHandlerClients = []

class loadWavdata(threading.Thread):

	def __init__(self):
		global flagWavstart
		threading.Thread.__init__(self)
		#self.inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NORMAL, channels=1, rate=8000, format=alsaaudio.PCM_FORMAT_FLOAT_LE, periodsize=256, device=config['AUDIO']['inputdevice'])
		self.inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NORMAL, device=config['AUDIO']['inputdevice'])
		self.inp.setchannels(1)
		self.inp.setrate(8000)
		self.inp.setformat(alsaaudio.PCM_FORMAT_FLOAT_LE)
		self.inp.setperiodsize(256)
		print('recording...')

	def run(self):
		global Wavframes, flagWavstart
		ret=b''
		while True:
			while not flagWavstart:
				time.sleep(0.5)
			l, ret = self.inp.read()
			if l > 0:
				for c in AudioRXHandlerClients:
					c.Wavframes.append(ret)
			else:
				print("overrun")
				time.sleep(0.01)
				

class WS_AudioRXHandler(tornado.websocket.WebSocketHandler):

	def open(self):
		self.set_nodelay(True)
		global flagWavstart
		if self not in AudioRXHandlerClients:
			AudioRXHandlerClients.append(self)
		self.Wavframes = []
		print('new connection on AudioRXHandler socket.')
		flagWavstart = True
		self.tailstream()
		self.set_nodelay(True)
		
	@tornado.gen.coroutine
	def tailstream(self):
		while flagWavstart:
			while len(self.Wavframes)==0:
				yield tornado.gen.sleep(0.1)
			yield self.write_message(self.Wavframes[0],binary=True)
			del self.Wavframes[0] 
			
	def on_close(self):
		if self in AudioRXHandlerClients:
			AudioRXHandlerClients.remove(self)
		global flagWavstart
		print('connection closed for audioRX')
		if len(AudioRXHandlerClients)<=0:
			flagWavstart = False
		self.Wavframes = []
		gc.collect()

############ websocket for control TX ##############
last_AudioTXHandler_msg_time=0
AudioTXHandlerClients = []

class WS_AudioTXHandler(tornado.websocket.WebSocketHandler):

	def stoppttontimeout(self):
		global last_AudioTXHandler_msg_time
		try:
			if time.time() > last_AudioTXHandler_msg_time + 10:
				if self.ws_connection and CTRX.infos["PTT"]==True:
					CTRX.setPTT("false")
					print("stop ptt on timeout")
		except:
			return None
		tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=1), self.stoppttontimeout)


	def TX_init(self, msg) :

		itrate, is_encoded, op_rate, op_frm_dur = [int(i) for i in msg.split(',')]
		self.is_encoded = is_encoded
		self.decoder = OpusDecoder(op_rate, 1)
		self.frame_size = op_frm_dur * op_rate

		device = config['AUDIO']['outputdevice']
		self.inp = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, alsaaudio.PCM_NONBLOCK, channels=1, rate=itrate, format=alsaaudio.PCM_FORMAT_S16_LE, periodsize=2048, device=device)		
	
	def open(self):
		global last_AudioTXHandler_msg_time, AudioTXHandlerClients
		if self not in AudioTXHandlerClients:
			AudioTXHandlerClients.append(self)
			print('new connection on AudioTXHandler socket.')
		last_AudioTXHandler_msg_time=time.time()
		self.stoppttontimeout()
		self.set_nodelay(True)

	def on_message(self, data) :
		global last_AudioTXHandler_msg_time
		last_AudioTXHandler_msg_time=time.time()
		
		if str(data).startswith('m:') :
			self.TX_init(str(data[2:]))
		elif str(data).startswith('s:') :
			self.inp.close()
		else :
			if self.is_encoded :
				pcm = self.decoder.decode(data, self.frame_size, False)
				self.inp.write(pcm)
				gc.collect()

			else :
				self.inp.write(data)
				gc.collect()

	def on_close(self):
		global AudioTXHandlerClients
		if(hasattr(self,"inp")):
			self.inp.close()
		if self in AudioTXHandlerClients:
			AudioTXHandlerClients.remove(self)
		if (not len(AudioTXHandlerClients)) and (CTRX.infos["PTT"]==True):
			CTRX.setPTT("false")
		print('connection closed for TX socket')		

############ websocket for control TRX ##############
ControlTRXHandlerClients = []
LastPing = time.time()

class TRXRIG:
	def __init__(self):
		self.spoints = {"0":-54, "1":-48, "2":-42, "3":-36, "4":-30, "5":-24, "6":-18, "7":-12, "8":-6, "9":0, "10":10, "20":20, "30":30, "40":40, "50":50, "60":60}
		self.infos = {}
		self.infos["PTT"]=False
		self.infos["powerstat"]=False
		self.serialport = Hamlib.hamlib_port_parm_serial
		self.serialport.rate=config['HAMLIB']['rig_rate']
		try:
			Hamlib.rig_set_debug(Hamlib.RIG_DEBUG_NONE)
			self.rig_model = "RIG_MODEL_"+str(config['HAMLIB']['rig_model'])
			self.rig_pathname = config['HAMLIB']['rig_pathname']
			self.rig = Hamlib.Rig(Hamlib.__dict__[self.rig_model])  # Look up the model's numerical index in Hamlib's symbol dictionary.
			self.rig.set_conf("rig_pathname", self.rig_pathname)
			if(config['HAMLIB']['rig_rate']!=""):
				self.rig.set_conf("serial_speed", str(config['HAMLIB']['rig_rate']))
			if(config['HAMLIB']['data_bits']!=""): 
				self.rig.set_conf("data_bits", str(config['HAMLIB']['data_bits'])) #8 as default
			if(config['HAMLIB']['stop_bits']!=""):
				self.rig.set_conf("stop_bits", str(config['HAMLIB']['stop_bits'])) #2 as default
			if(config['HAMLIB']['serial_parity']!=""):
				self.rig.set_conf("serial_parity", str(config['HAMLIB']['serial_parity']))# None as default NONE ODD EVEN MARK SPACE
			if(config['HAMLIB']['serial_handshake']!=""):
				self.rig.set_conf("serial_handshake", str(config['HAMLIB']['serial_handshake'])) # None as default NONE XONXOFF HARDWARE 
			if(config['HAMLIB']['dtr_state']!=""):
				self.rig.set_conf("dtr_state", str(config['HAMLIB']['dtr_state'])) #ON or OFF
			if(config['HAMLIB']['rts_state']!=""):
				self.rig.set_conf("rts_state", str(config['HAMLIB']['rts_state'])) #ON or OFF
			self.rig.set_conf("retry", config['HAMLIB']['retry'])
			self.rig.open()
		except:
			print("Could not open a communication channel to the rig via Hamlib!")
		
		self.setPower(1)
		self.getvfo()
		self.getFreq()
		self.getMode()
		
	def parsedbtospoint(self,spoint):
		for key, value in self.spoints.items():
			if (spoint<value):
				return key
				break
				
		
	def getvfo(self):
		try:
			self.infos["VFO"] = (self.rig.get_vfo())
		except:
			print("Could not obtain the current VFO via Hamlib!")
		return self.infos["VFO"]

	def setFreq(self,frequency):
		try:
			self.rig.set_freq(Hamlib.RIG_VFO_CURR, float(frequency))
			self.getFreq()
		except:
			print("Could not set the frequency via Hamlib!")
		return self.infos["FREQ"]
		
	def getFreq(self):
		try:
			self.infos["FREQ"] = (int(self.rig.get_freq()))
		except:
			print("Could not obtain the current frequency via Hamlib!")
		return self.infos["FREQ"]
		
	def setMode(self,MODE):
		try:
		    
			self.rig.set_mode(Hamlib.rig_parse_mode(MODE))
			self.getMode()
		except:
			print("Could not set the mode via Hamlib!")
		return self.infos["MODE"]
		
	def getMode(self):
		try:
			(mode, width) = self.rig.get_mode()
			self.infos["MODE"] = Hamlib.rig_strrmode(mode).upper()
			self.infos["WIDTH"] = width
		except:
			print("Could not obtain the current Mode via Hamlib!")
		return self.infos["MODE"]
		return ""

	def getStrgLVL(self):
		try:
			self.infos["StrgLVLi"] = self.rig.get_level_i(Hamlib.RIG_LEVEL_STRENGTH)
			self.infos["StrgLVL"] = self.parsedbtospoint(self.infos["StrgLVLi"])
		except:
			print("Could not obtain the current Strength signal RX level via Hamlib!")
		return self.infos["StrgLVL"]
		
	def setPTT(self,status):
		try:
			if status == "true":
				self.rig.set_ptt(Hamlib.RIG_VFO_CURR,Hamlib.RIG_PTT_ON)
				self.infos["PTT"]=True
			else:
				self.rig.set_ptt(Hamlib.RIG_VFO_CURR,Hamlib.RIG_PTT_OFF)
				self.infos["PTT"]=False
		except:
			print("Could not set the mode via Hamlib!")
		return self.infos["PTT"]
		
	def getPTT(self,status):
		return self.infos["PTT"]
		
	def setPower(self,status=1):
		try:
			if status:
				self.rig.set_powerstat(Hamlib.RIG_POWER_ON)
			else:
				self.rig.set_powerstat(Hamlib.RIG_POWER_OFF)
			self.infos["powerstat"] = status
		except:
			print("Could not set power status via Hamlib!")
		return self.infos["powerstat"]

class ticksTRXRIG(threading.Thread):

	def __init__(self):
		threading.Thread.__init__(self)

	def run(self):
		while True:
			if CTRX.infos["powerstat"]:
				CTRX.getStrgLVL()
			time.sleep(0.1)

class WS_ControlTRX(tornado.websocket.WebSocketHandler):

	def send_to_all_clients(self,msg):
		print ("Send to all: "+msg)
		for client in ControlTRXHandlerClients:
			client.write_message(msg)

	def sendPTINFOS(self):
		try:
			if self.StrgLVL != CTRX.infos["StrgLVL"]:
				self.write_message("getSignalLevel:"+str(CTRX.infos["StrgLVL"]))
				self.StrgLVL=CTRX.infos["StrgLVL"]
		except:
			print("error TXMETER")
			return None
		tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=float(config['CTRL']['interval_smeter_update'])), self.sendPTINFOS)

	def open(self):
		if self not in ControlTRXHandlerClients:
			ControlTRXHandlerClients.append(self)
			self.StrgLVL=0
			self.sendPTINFOS()
		CTRX.setPower(1)
		print('new connection on ControlTRX socket.')
		if(is_rtlsdr_present):
			self.write_message("panfft")
		self.set_nodelay(True)
		
	@tornado.gen.coroutine
	def on_message(self, data) :
		global LastPing
		if bool(config['CTRL']['debug']):
			print(data)
		
		try:
			(action, datato) = data.split(':')
		except ValueError:
			action = data
			pass
			
		if(action == "PING"):
				self.write_message("PONG")
		elif(action == "getFreq"):
			yield self.send_to_all_clients("getFreq:"+str(CTRX.getFreq()))
		elif(action == "setFreq"):
			yield self.send_to_all_clients("getFreq:"+str(CTRX.setFreq(datato)))
		elif(action == "getMode"):
			yield self.send_to_all_clients("getMode:"+str(CTRX.getMode()))	
		elif(action == "setMode"):
			yield self.send_to_all_clients("getMode:"+str(CTRX.setMode(datato)))
		elif(action == "setPTT"):
			yield self.send_to_all_clients("getPTT:"+str(CTRX.setPTT(datato)))	

		LastPing = time.time();
			
	def on_close(self):
		if self in ControlTRXHandlerClients:
			ControlTRXHandlerClients.remove(self)	
		gc.collect()

def timeoutTRXshutdown():
	global LastPing
	if(LastPing+300) < time.time():
		print("Shutdown TRX")
		CTRX.setPower(0)

class threadtimeoutTRXshutdown(threading.Thread):

	def __init__(self):
		threading.Thread.__init__(self)

	def run(self):
		while True:
			time.sleep(60)
			timeoutTRXshutdown()

############ Config ##############
class ConfigHandler(BaseHandler):
	def get(self):
	
		if bool(config['SERVER']['auth']) and not self.current_user:
			self.redirect("/login")
			return
	
		self.application.settings.get("compiled_template_cache", False)
		self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
		try:
			from serial.tools.list_ports import comports
		except ImportError:
			return None
		audiodevicesoutput=[s for s in alsaaudio.pcms(0) if "plughw" in s]
		audiodevicesinput=[s for s in alsaaudio.pcms(1) if "plughw" in s]
		comports=list(comports())
		rig_models=[s[10:] for s in dir(Hamlib) if "RIG_MODEL_" in s]
		self.write("""<html><form method="POST" action="/CONFIG">""")
		self.write("""[SERVER]<br/><br/>""")
		self.write("""SERVER TCP/IP port:<input type="text" name="SERVER.port" value="""+config['SERVER']['port']+""">Defautl:<b>8888</b>.The server port<br/><br/>""")
		self.write("""SERVER Authentification type:<input type="text" name="SERVER.auth" value="""+config['SERVER']['auth']+"""> Defautl:<b>leave blank</b>. Else you can use "FILE" or/and "PAM".<br/><br/>""")
		self.write("""SERVER database users file:<input type="text" name="SERVER.db_users_file" value="""+config['SERVER']['db_users_file']+"""> Defautl:<b>UHRR_users.db</b> Only if you use Authentification type "FILE".<br/><br/>""")
		self.write("""You can change database users file in UHRR.conf.<br/> To add a user in FILE type, add it in UHRR_users.db (default file name).<br/>Add one account per line as login password.<br/>""")
		self.write("""If you plan to use PAM you can add account in command line: adduser --no-create-home --system thecallsign.<br/><br/>""")
		self.write("""If you want to change certfile and keyfile, replace "UHRH.crt" and "UHRH.key" in the boot folder, and when the pi boot, it will use those files to start http ssl.<br/><br/>""")

		self.write("""[AUDIO]<br/><br/>""")
		self.write("""AUDIO outputdevice:<select name="AUDIO.outputdevice">""")
		if(config['AUDIO']['outputdevice']!=""):
			self.write("""<option value="""+config['AUDIO']['outputdevice']+""" selected>"""+config['AUDIO']['outputdevice']+"""</option>""")
		for c in audiodevicesoutput:
			self.write("""<option value="""+c+""">"""+c+"""</option>""")
		self.write("""</select> Output from audio soundcard to the mic input of TRX.<br/><br/>""")
		
		self.write("""AUDIO inputdevice:<select name="AUDIO.inputdevice">""")
		if(config['AUDIO']['inputdevice']!=""):
			self.write("""<option value="""+config['AUDIO']['inputdevice']+""" selected>"""+config['AUDIO']['inputdevice']+"""</option>""")
		for c in audiodevicesinput:
			self.write("""<option value="""+c+""">"""+c+"""</option>""")
		self.write("""</select> Input from audio soundcard from the speaker output of TRX.<br/><br/>""")

		self.write("""[HAMLIB]<br/><br/>""")
	
		self.write("""HAMLIB radio model:<select name="HAMLIB.rig_model">""")
		if(config['HAMLIB']['rig_model']!=""):
			self.write("""<option value="""+config['HAMLIB']['rig_model']+""" selected>"""+config['HAMLIB']['rig_model']+"""</option>""")
		for c in rig_models:
			self.write("""<option value="""+c+""">"""+c+"""</option>""")
		self.write("""</select> Hamlib trx model.<br/><br/>""")
		
		self.write("""HAMLIB serial port:<select name="HAMLIB.rig_pathname">""")
		if(config['HAMLIB']['rig_pathname']!=""):
			self.write("""<option value="""+config['HAMLIB']['rig_pathname']+""" selected>"""+config['HAMLIB']['rig_pathname']+"""</option>""")
		for c in comports:
			self.write("""<option value="""+str(c.device)+""">"""+str(c.device)+"""</option>""")
		self.write("""</select> Serial port of the CAT interface.<br/><br/>""")
		
		self.write("""HAMLIB radio rate:<select name="HAMLIB.rig_rate">""")
		if(config['HAMLIB']['rig_rate']!=""):
			self.write("""<option value="""+config['HAMLIB']['rig_rate']+""" selected>"""+config['HAMLIB']['rig_rate']+"""</option>""")
		self.write("""<option value=230400>230400</option>""")
		self.write("""<option value=115200>115200</option>""")
		self.write("""<option value=57600>57600</option>""")
		self.write("""<option value=38400>38400</option>""")
		self.write("""<option value=19200>19200</option>""")
		self.write("""<option value=9600>9600</option>""")
		self.write("""<option value=4800>4800</option>""")
		self.write("""<option value=2400>2400</option>""")
		self.write("""<option value=1200>1200</option>""")
		self.write("""<option value=600>600</option>""")
		self.write("""<option value=300>300</option>""")
		self.write("""<option value=150>150</option>""")
		self.write("""</select> Serial port baud rate.<br/><br/>""")
		
		self.write("""HAMLIB auto tx poweroff:<select name="HAMLIB.trxautopower">""")
		if(config['HAMLIB']['trxautopower']!=""):
			self.write("""<option value="""+config['HAMLIB']['trxautopower']+""" selected>"""+config['HAMLIB']['trxautopower']+"""</option>""")
		self.write("""<option value=\"True\">True</option>""")
		self.write("""<option value=\"False\">False</option>""")
		self.write("""</select> Set to auto power off the trx when it's not in use<br/><br/>""")

		CDVALUE=""
		if(config['HAMLIB']['data_bits']!=""):
			CDVALUE=config['HAMLIB']['data_bits']
		self.write("""HAMLIB serial data bits:<input type="text" name="HAMLIB.data_bits" value="""+CDVALUE+"""> Leave blank to use the HAMIB default value.<br/><br/>""")
		
		CDVALUE=""
		if(config['HAMLIB']['stop_bits']!=""):
			CDVALUE=config['HAMLIB']['stop_bits']
		self.write("""HAMLIB serial stop bits:<input type="text" name="HAMLIB.stop_bits" value="""+CDVALUE+"""> Leave blank to use the HAMIB default value.<br/><br/>""")

		self.write("""HAMLIB serial parity:<select name="HAMLIB.serial_parity">""")
		if(config['HAMLIB']['serial_parity']!=""):
			self.write("""<option value="""+config['HAMLIB']['serial_parity']+""" selected>"""+config['HAMLIB']['serial_parity']+"""</option>""")
		self.write("""<option value=\"\"></option>""")
		self.write("""<option value=\"None\">None</option>""")
		self.write("""<option value=\"Odd\">Odd</option>""")
		self.write("""<option value=\"Even\">Even</option>""")
		self.write("""<option value=\"Mark\">Mark</option>""")
		self.write("""<option value=\"Space\">Space</option>""")
		self.write("""</select> Leave blank to use the HAMIB default value.<br/><br/>""")

		self.write("""HAMLIB serial handshake:<select name="HAMLIB.serial_handshake">""")
		if(config['HAMLIB']['serial_handshake']!=""):
			self.write("""<option value="""+config['HAMLIB']['serial_handshake']+""" selected>"""+config['HAMLIB']['serial_handshake']+"""</option>""")
		self.write("""<option value=\"\"></option>""")
		self.write("""<option value=\"None\">None</option>""")
		self.write("""<option value=\"XONXOFF\">XONXOFF</option>""")
		self.write("""<option value=\"Hardware\">Hardware</option>""")
		self.write("""</select> Leave blank to use the HAMIB default value.<br/><br/>""")

		self.write("""HAMLIB dtr state:<select name="HAMLIB.dtr_state">""")
		if(config['HAMLIB']['dtr_state']!=""):
			self.write("""<option value="""+config['HAMLIB']['dtr_state']+""" selected>"""+config['HAMLIB']['dtr_state']+"""</option>""")
		self.write("""<option value=\"\"></option>""")
		self.write("""<option value=\"ON\">ON</option>""")
		self.write("""<option value=\"OFF\">OFF</option>""")
		self.write("""</select> Leave blank to use the HAMIB default value.<br/><br/>""")

		self.write("""HAMLIB rts state:<select name="HAMLIB.rts_state">""")
		if(config['HAMLIB']['rts_state']!=""):
			self.write("""<option value="""+config['HAMLIB']['rts_state']+""" selected>"""+config['HAMLIB']['rts_state']+"""</option>""")
		self.write("""<option value=\"\"></option>""")
		self.write("""<option value=\"ON\">ON</option>""")
		self.write("""<option value=\"OFF\">OFF</option>""")
		self.write("""</select> Leave blank to use the HAMIB default value.<br/><br/>""")

		self.write("""[PANADAPTER]<br/><br/>""")
		self.write("""PANADAPTER FI frequency (hz):<input type="text" name="PANADAPTER.center_freq" value="""+config['PANADAPTER']['center_freq']+"""><br/><br/>""")
		
		self.write("""HAMLIB radio rate (samples/s):<select name="PANADAPTER.sample_rate">""")
		if(config['PANADAPTER']['sample_rate']!=""):
			self.write("""<option value="""+config['PANADAPTER']['sample_rate']+""" selected>"""+config['PANADAPTER']['sample_rate']+"""</option>""")
		self.write("""<option value=3200000>3200000</option>""")
		self.write("""<option value=2880000>2880000</option>""")
		self.write("""<option value=2400000>2400000</option>""")
		self.write("""<option value=1800000>1800000</option>""")
		self.write("""<option value=1440000>1440000</option>""")
		self.write("""<option value=1200000>1200000</option>""")
		self.write("""<option value=1020000>1020000</option>""")
		self.write("""<option value=960000>960000</option>""")
		self.write("""</select><br/><br/>""")
		
		self.write("""PANADAPTER frequency correction (ppm):<input type="text" name="PANADAPTER.freq_correction" value="""+config['PANADAPTER']['freq_correction']+"""><br/><br/>""")

		self.write("""PANADAPTER initial gain:<input type="text" name="PANADAPTER.gain" value="""+config['PANADAPTER']['gain']+"""><br/><br/>""")
		
		self.write("""PANADAPTER windowing:<select name="PANADAPTER.fft_window">""")
		if(config['PANADAPTER']['fft_window']!=""):
			self.write("""<option value="""+config['PANADAPTER']['fft_window']+""" selected>"""+config['PANADAPTER']['fft_window']+"""</option>""")
		self.write("""<option value="bartlett">bartlett</option>""")
		self.write("""<option value="blackman">blackman</option>""")
		self.write("""<option value="hamming">hamming</option>""")
		self.write("""<option value="hanning">hanning</option>""")
		self.write("""</select><br/><br/>""")
		
		self.write("""<input type="submit" value="Save & Restart server"><br/><br/></form>Possible problem:"""+e+"""</html>""")
		
	def post(self):
	
		if bool(config['SERVER']['auth']) and not self.current_user:
			self.redirect("/login")
			return
			
		for x in self.request.arguments:
			(s,o)=x.split(".")
			v=self.get_argument(x)
			print(s,o,v)
			if config.has_option(s,o):
				config[s][o]=v
		with open('UHRR.conf', 'w') as configfile:
			config.write(configfile)
		self.write("""<html><head><script>window.setTimeout(function() {window.location.href = 'https://'+window.location.hostname+':'+ '"""+config['SERVER']['port']+"""';}, 10000);</script><head><body>You will be redirected automatically. Please wait...<br><img width="40px" height=40px" src="../img/spinner.gif"></body></html>""")
		self.flush() 
		time.sleep(2)
		os.system("sleep 2;./UHRR &")
		os._exit(1)
		
############ Login ##############
class AuthLoginHandler(BaseHandler):

	def get(self):
		if not bool(config['SERVER']['auth']):
			self.redirect("/")
			return
		self.write('<html><body><form action="/login" method="post">'
					'CallSign: <input type="text" name="name"></br>'
					'Password: <input type="password" name="passwd"></br>'
					'<input type="submit" value="Sign in">'
					'</form></body></html>')

	def post(self):
		if self.get_argument("name") != "" and self.get_argument("passwd") != "":
			if self.bind(self.get_argument("name"),self.get_argument("passwd")):
				self.set_secure_cookie("user", self.get_argument("name"))
				self.set_cookie("callsign", self.get_argument("name"))
				self.set_cookie("autha", "1")
			else:
				writte_log("Auth error for CallSign:"+str(self.get_argument("name")))
		self.redirect("/")
		
	def bind(self,user="",password=""):
		retval = False
		if (user!="" and password!=""):
			if config['SERVER']['auth'].find("FILE") != -1: #test with users db file
				f = open(config['SERVER']['db_users_file'], "r") 
				for x in f:
					if x[0]!="#":
						db=x.strip('\n').split(" ")
						if db[0] == user and db[1]== password:
							retval = True
							break		
			if not retval and config['SERVER']['auth'].find("PAM") != -1:#test with pam module
				if config['SERVER']['pam_account'].find(user) != -1:
					import pam
					retval = pam.authenticate(user, password) 
		return retval
		
class AuthLogoutHandler(BaseHandler):
	def get(self):
		self.clear_cookie("user")
		self.clear_cookie("autha")
		self.redirect(self.get_argument("next", "/"))
		
############ Main ##############
class MainHandler(BaseHandler):

	def get(self):
		print("Tornado current user:"+str(self.current_user))
		if bool(config['SERVER']['auth']) and not self.current_user:
			self.redirect("/login")
			return
		self.application.settings.get("compiled_template_cache", False)
		self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
		self.render("www/index.html")

if __name__ == "__main__":	
	
	try:
		if is_rtlsdr_present:
			threadFFT = loadFFTdata()
			threadFFT.start()
			
		threadloadWavdata = loadWavdata()
		threadloadWavdata.start()
		
		CTRX = TRXRIG()
		
		threadticksTRXRIG = ticksTRXRIG()
		threadticksTRXRIG.start()
		
		if(config['HAMLIB']['trxautopower']=="True"):
			threadsurveilTRX = threadtimeoutTRXshutdown()
			threadsurveilTRX.start()
		
		
		app = tornado.web.Application([
		(r'/login', AuthLoginHandler),
		(r'/logout', AuthLogoutHandler),
		(r'/WSaudioRX', WS_AudioRXHandler),
		(r'/WSaudioTX', WS_AudioTXHandler),
		(r'/WSCTRX', WS_ControlTRX),
		(r'/WSpanFFT', WS_panFFTHandler),
		(r'/(panfft.*)', tornado.web.StaticFileHandler, { 'path' : './www/panadapter' }),
		(r'/CONFIG', ConfigHandler),
		(r'/', MainHandler),
		(r'/(.*)', tornado.web.StaticFileHandler, { 'path' : './www' })
		],debug=bool(config['SERVER']['debug']), websocket_ping_interval=10, cookie_secret=config['SERVER']['cookie_secret'])
	except:
		e = str(sys.exc_info())
		print(e)
		app = tornado.web.Application([
		(r'/CONFIG', ConfigHandler),
		(r'/', ConfigHandler),
		(r'/(.*)', tornado.web.StaticFileHandler, { 'path' : './www' })
		],debug=bool(config['SERVER']['debug']))

	http_server = tornado.httpserver.HTTPServer(app, ssl_options={
	"certfile": os.path.join(config['SERVER']['certfile']),
	"keyfile": os.path.join(config['SERVER']['keyfile']),
	})
	http_server.listen(int(config['SERVER']['port']))
	print('HTTP server started.')
	tornado.ioloop.IOLoop.instance().start()

