import time
from struct import *
import ctypes
import array
import numpy as np
import matplotlib.pyplot as plt
import os

import eel # GUI

from ADUCv2p1 import *

###################################
## PYTHON UTILITIES
###################################
last_wf_number = 0
last_timestamp = 0
def save_waveform():
	global last_timestamp, last_wf_number
	chip = chips[state["selected_board"]]
	data = chip.read_data()
	gnd = sum(data[state["start_gnd"]:state["stop_gnd"]])/(state["stop_gnd"]-state["start_gnd"])
	signal = sum(data[state["start"]:state["stop"]])/(state["stop"]-state["start"])
	gnd_std = np.std(data[state["start_gnd"]:state["stop_gnd"]])
	signal_std = np.std(data[state["start"]:state["stop"]])
	wf_number = chip.get_wf_cnt()
	timestamp = time.time()
	if(last_wf_number>0 and wf_number - last_wf_number>0):
		state["board_freq"] = (wf_number - last_wf_number)/(timestamp - last_timestamp)
	else:
		state["board_freq"] = 0
	last_wf_number, last_timestamp = wf_number, timestamp
	tosave = [wf_number, timestamp, state["start"], state["stop"], state["start_gnd"], state["stop_gnd"], state["input_gain"], state["offset"], signal, gnd, signal_std, gnd_std, state["wf_len"]] + list(data)
	np.savetxt("data/waveforms/"+str(int(timestamp*100))+".csv", [tosave], delimiter=",", fmt='%10.5f')
	# Save more data for long term graphs
	tosave = [wf_number, timestamp, signal, gnd, signal-gnd, signal_std, gnd_std, chip.get_mode(), chip.get_out_of_lock(), chip.get_Vout(1), chip.get_Vout(2)]
	line = '\n'+','.join(map(str, tosave))
	with open('data/long_term.csv','a') as fd:
		fd.write(line)

	#update_state()

import datetime
@eel.expose
def clean_old_files(path, max_Files):
	def sorted_ls(path):
	    mtime = lambda f: os.stat(os.path.join(path, f)).st_mtime
	    return list(sorted(os.listdir(path), key=mtime))
	 
	del_list = sorted_ls(path)[0:(len(sorted_ls(path))-max_Files)] 
	for dfile in del_list:
	    os.remove(path + dfile)

	'''for dirpath, dirnames, filenames in os.walk("data/waveforms/"):
		for file in filenames:
			curpath = os.path.join(dirpath, file)
			file_modified = datetime.datetime.fromtimestamp(os.path.getmtime(curpath))
			if datetime.datetime.now() - file_modified > datetime.timedelta(hours=10):
				os.remove(curpath)'''

@eel.expose
def clean_long_term():
	with open('data/long_term.csv','w') as fd:
		fd.write('')


@eel.expose
def calibrate_gain():
	if(state["mode"]!=0):
		error("You must be in learn mode to calibrate the loop gain")
		return
	message("Calibrating...")
	chip = chips[state["selected_board"]]
	auto_set_pga = state["auto_set_pga"]
	chip.set_auto_set_pga(0) #Stop it while we scan

	measurements = []
	for Vfine in range(2000, 4000, 100):
		chip.set_Vout(2, Vfine)
		time.sleep(0.1)
		data = chip.read_data()
		gnd = sum(data[state["start_gnd"]:state["stop_gnd"]])/(state["stop_gnd"]-state["start_gnd"])
		signal = sum(data[state["start"]:state["stop"]])/(state["stop"]-state["start"])
		print('Vfine', chip.get_Vout(2), 'signal', signal-gnd)
		measurements.append([chip.get_Vout(2), signal-gnd])
	chip.set_Vout(2, 3000)
	
	measurements = np.array(measurements)
	p = np.polyfit(measurements[:,0], measurements[:,1], deg=1)
	print('signal-gnd vs Vfine slope', p[0], 'intercept', p[1])
	chip.set_Gain(1/p[0])
	print('Gain has been set to 1/slope', 1/p[0])

	plt.scatter(measurements[:,0], measurements[:,1], label='Measurements')
	plt.plot(np.arange(2000, 4000, 100), np.arange(2000, 4000, 100)*p[0] + p[1], label='Linear fit')
	plt.legend()
	plt.xlabel('Fine voltage')
	plt.ylabel('Photodiode signal')
	plt.show()

	chip.set_auto_set_pga(auto_set_pga)
	update_state()

@eel.expose
def calibrate_coarse_fine_ratio():
	if(state["mode"]!=0):
		error("You must be in learn mode to calibrate the broad to fine ratio")
		return
	message("Calibrating...")
	chip = chips[state["selected_board"]]
	auto_set_pga = state["auto_set_pga"]
	chip.set_auto_set_pga(0) #Stop it while we scan

	# Scan Vfine
	measurements = []
	for Vfine in range(2500, 3500, 100):
		chip.set_Vout(2, Vfine)
		time.sleep(0.1)
		data = chip.read_data()
		gnd = sum(data[state["start_gnd"]:state["stop_gnd"]])/(state["stop_gnd"]-state["start_gnd"])
		signal = sum(data[state["start"]:state["stop"]])/(state["stop"]-state["start"])
		print('Vfine', chip.get_Vout(2), 'signal', signal-gnd)
		measurements.append([chip.get_Vout(2), signal-gnd])
	chip.set_Vout(2, 3000)
	measurements = np.array(measurements)
	p = np.polyfit(measurements[:,0], measurements[:,1], deg=1)
	# Scan Vcoarse
	measurements = []
	low, high = state["Vlearn"]-100, state["Vlearn"]+100
	if(low<0):
		low=0
	if(high>4000):
	 high=4000
	for Vcoarse in range(low, high, 20):
		chip.set_Vlearn(Vcoarse)
		time.sleep(0.1)
		data = chip.read_data()
		gnd = sum(data[state["start_gnd"]:state["stop_gnd"]])/(state["stop_gnd"]-state["start_gnd"])
		signal = sum(data[state["start"]:state["stop"]])/(state["stop"]-state["start"])
		print('Vcoarse', chip.get_Vlearn(), 'signal', signal-gnd)
		measurements.append([chip.get_Vlearn(), signal-gnd])
	chip.set_Vlearn(state["Vlearn"])
	measurements = np.array(measurements)
	q = np.polyfit(measurements[:,0], measurements[:,1], deg=1)
	# Set to ratio
	print('Setting coarse_fine_ratio to', q[0]/p[0])
	chip.set_coarse_fine_ratio(q[0]/p[0])

	chip.set_auto_set_pga(auto_set_pga)
	update_state()

@eel.expose
def measure_response_function():
	message("Measuring...")
	if(state["mode"]!=0):
		error("You must be in learn mode to measure the response function.")
		return
	chip = chips[state["selected_board"]]
	auto_set_pga = state["auto_set_pga"]
	chip.set_auto_set_pga(0) #Stop it while we scan

	# Scan Vcoarse
	measurements = []
	for Vcoarse in range(0, 4000, 100):
		chip.set_Vlearn(Vcoarse)
		time.sleep(0.1)
		data = chip.read_data()
		gnd = sum(data[state["start_gnd"]:state["stop_gnd"]])/(state["stop_gnd"]-state["start_gnd"])
		signal = sum(data[state["start"]:state["stop"]])/(state["stop"]-state["start"])
		print('Vcoarse', chip.get_Vlearn(), 'signal', signal-gnd)
		measurements.append([chip.get_Vlearn(), signal-gnd])
	chip.set_Vlearn(state["Vlearn"])
	measurements = np.array(measurements)
	
	plt.plot(measurements[:,0], measurements[:,1])
	plt.xlabel('Output voltage')
	plt.ylabel('Photodiode signal')
	plt.show()

	chip.set_auto_set_pga(auto_set_pga)
	update_state()

@eel.expose
def sdev_time():
	pass

@eel.expose
def noise_power_spectrum():
	pass


###################################
## EXPOSED GUI COMMUNICATION
###################################

# Graphs
from os import listdir
@eel.expose
def listFiles(path):
	return listdir(path)
@eel.expose
def loadFile(path):
	try:
		return list(np.loadtxt(path, delimiter=","))
	except:
		print('Could not load file', path)
@eel.expose
def loadLongTerm():
	try:
		li = list(np.loadtxt('data/long_term.csv', delimiter=","))
		if((li[-1][1]-li[0][1])/60/60 > 24 or len(li)>100000): # Time to clean up, delete until we have less than 90000 points and 20 hours of data
			with open('data/long_term.csv') as f, open("data/long_term_tmp.csv", "w") as out:
				for x in range(len(li)):
					if(len(li)-x>90000 or (li[-1][1]-li[x][1])/60/60 > 20):
						next(f)
				for line in f:
					out.write(line)
				os.remove("data/long_term.csv")
				os.rename("data/long_term_tmp.csv", "data/long_term.csv")
		return [list(l) for l in li]
	except:
		print('Couldnt load this')

# Expose functions to GUI and do some parameter checking
def error(txt):
	eel.error(txt)
	eel.renderUI(state)
def warning(txt):
	eel.warning(txt)
def message(txt):
	eel.message(txt)

@eel.expose
def set_selected_board(n):
	if n>len(chips):
		error("Selected board does not exist.")
		return;
	state["selected_board"] = n
	update_state()
@eel.expose
def set_pi_freq(freq):
	if(freq>100):
		error("The communication cannot be this fast. High values are likely to disturb the board.")
		return
	if(freq<0.01):
		error("Frequency must be at least 0.01Hz.")
		return
	if(freq>10):
		warning("High values are likely to disturb the board.")
	state["pi_freq"] = freq
	eel.renderUI(state)
@eel.expose
def set_remote_trigg(status):
	status = int(status)
	if status not in [0, 1]:
		error("remote_trigg must be set to either 0 or 1.")
		return
	chips[state["selected_board"]].set_remote_trigg(status)
	update_state()
@eel.expose
def set_enab_gnd(status):
	status = int(status)
	if status not in [0, 1]:
		error("enab_gnd must be set to either 0 or 1.")
		return
	chips[state["selected_board"]].set_enab_gnd(status)
	update_state()
@eel.expose
def set_Vlearn(Vlearn):
	if Vlearn>4095 or Vlearn<0: 
		error("Vlearn must be in the range 0-4095")
		return
	if Vlearn<2000: 
		warning("Low values of Vlearn will heavily attenuate the output. See the response curve.")
	if Vlearn>3500: 
		warning("High values of Vlearn may not leave enough room for the stabilization process and may result in the board going out of loop. See the response curve.")
	chips[state["selected_board"]].set_Vlearn(Vlearn)
	update_state()
@eel.expose
def set_start(start):
	if start>255 or start<0: 
		error("start must be in the range 0-256")
		return
	if start>state["stop"]: 
		error("start must be lower than stop")
		return
	if start>state["wf_len"]: 
		error("start must be lower than the waveform length")
		return
	chips[state["selected_board"]].set_start(start)
	update_state()
@eel.expose
def set_stop(stop):
	if stop>255 or stop<0: 
		error("stop must be in the range 0-256")
		return
	if stop<state["start"]: 
		error("start must be lower than stop")
		return
	if stop>state["wf_len"]: 
		error("stop must be lower than the waveform length")
		return
	chips[state["selected_board"]].set_stop(stop)
	update_state()
@eel.expose
def set_start_gnd(start_gnd):
	if start_gnd>255 or start_gnd<0: 
		error("start_gnd must be in the range 0-256")
		return
	if start_gnd>state["stop_gnd"]: 
		error("start_gnd must be lower than stop")
		return
	if start_gnd>state["wf_len"]: 
		error("start_gnd must be lower than the waveform length")
		return
	chips[state["selected_board"]].set_start_gnd(start_gnd)
	update_state()
@eel.expose
def set_stop_gnd(stop_gnd):
	if stop_gnd>255 or stop_gnd<0: 
		error("stop_gnd must be in the range 0-256")
		return
	if stop_gnd<state["start_gnd"]: 
		error("start must be lower than stop")
		return
	if stop_gnd>state["wf_len"]: 
		error("stop_gnd must be lower than the waveform length")
		return
	chips[state["selected_board"]].set_stop_gnd(stop_gnd)
	update_state()
@eel.expose
def set_wf_len(wf_len):
	if wf_len>255 or wf_len<0: 
		error("wf_len must be in the range 0-256")
		return
	if wf_len<state["stop"] or wf_len<state["stop_gnd"]: 
		error("wf_len must be higher than both stop and stop_gnd")
		return
	chips[state["selected_board"]].set_wf_len(wf_len)
	update_state()
@eel.expose
def set_N(N):
	N = int(N)
	if N > 5:
		warning("Note that increasing the Number of waveforms per stabilization loop decreases the speed of the stabilization. We typically just have N=1.")
	chips[state["selected_board"]].set_N(N)
	update_state()
@eel.expose
def set_step_max(step):
	if step>4095 or step<1: 
		error("step_max must be in the range 0-1")
		return
	if step<50:
		warning("step_max seems low. This may limit the ability to react to fast power fluctuations.")
	chips[state["selected_board"]].set_step_max(step)
	update_state()
@eel.expose
def set_Gain(*params):
	chips[state["selected_board"]].set_Gain(*params)
	update_state()
@eel.expose
def set_auto_set_pga(status):
	if status not in [0, 1]:
		error("auto_set_pga must be set to either 0 or 1.")
		return
	chips[state["selected_board"]].set_auto_set_pga(status)
	update_state()
@eel.expose
def set_input_gain(input_gain):
	if input_gain not in [1, 2, 4, 8, 16, 32, 64, 128]: 
		error("input_gain must be one of [1, 2, 4, 8, 16, 32, 64, 128]")
		return
	chips[state["selected_board"]].set_input_gain(input_gain)
	update_state()
@eel.expose
def set_offset(offset):
	if offset>4095 or offset<0: 
		error("offset must be in the range 0-4095. 2000 corresponds to no offset.")
		return
	chips[state["selected_board"]].set_offset(offset)
	update_state()
@eel.expose
def set_coarse_fine_ratio(coarse_fine_ratio):
	if coarse_fine_ratio<10 or coarse_fine_ratio>30: 
		error("coarse_fine_ratio should be approximately 20.")
		return
	if coarse_fine_ratio<15 or coarse_fine_ratio>25: 
		warning("coarse_fine_ratio should be approximately 20.")
	chips[state["selected_board"]].set_coarse_fine_ratio(coarse_fine_ratio)
	update_state()

# Program state
state = {
	"n_boards": 2,
	"selected_board":  0,
	"box_address": 0x50,
	"mode": 0,
	"out_of_lock": 0,
	"pi_freq": 1,
	"board_freq": -1,
	"remote_trigg": 0,
	"enab_gnd": 1,
	"Vlearn": 3200,
	"start": 25,
	"stop": 100,
	"start_gnd": 125,
	"stop_gnd": 200,
	"wf_len": 200,
	"N": 1,
	"step_max": 200,
	"Gain": 1.0,
	"auto_set_pga": 1,
	"input_gain": 1,
	"offset": 2000,
	"coarse_fine_ratio": 20
}

def update_state():
	state["n_boards"] = len(chips)
	state["box_address"] = adresses[state["selected_board"]]
	chip = chips[state["selected_board"]]
	state["enab_gnd"] = chip.get_enab_gnd()
	state["mode"] = chip.get_mode()
	state["out_of_lock"] = chip.get_out_of_lock()
	state["remote_trigg"] = chip.get_remote_trigg()	
	state["Vlearn"] = chip.get_Vlearn()
	state["start"] = chip.get_start()
	state["stop"] = chip.get_stop()
	state["start_gnd"] = chip.get_start_gnd()
	state["stop_gnd"] = chip.get_stop_gnd()
	state["wf_len"] = chip.get_wf_len()
	state["N"] = chip.get_N()
	state["step_max"] = chip.get_step_max()
	state["Gain"] = chip.get_Gain()
	state["auto_set_pga"] = chip.get_auto_set_pga()
	state["input_gain"] = chip.get_input_gain()
	state["offset"] = chip.get_offset()
	state["coarse_fine_ratio"] = chip.get_coarse_fine_ratio()
	for l in state:
            if isinstance(state[l], np.uint16) or isinstance(state[l], np.int32):
                state[l] = int(state[l])
            if isinstance(state[l], np.float32):
                state[l] = float(state[l])
	eel.renderUI(state)


###################################
## START THE GUI
###################################

my_options = {
	'mode': "chrome-app", #chrome-app
	'host': 'localhost',
	'port': 8000 + int(np.random.rand()*1000),
	'size':(660, 605),
	#'chromeFlags': ["--start-fullscreen", "--browser-startup-dialog"]
}


eel.init('web')
eel.start('main.html', options=my_options, block=False)

# Detect all boards
import os
import subprocess
import re

chips = []
adresses = []
# TO DO
p = subprocess.Popen(['i2cdetect', '-y','1'],stdout=subprocess.PIPE,)
p.stdout.readline()
for i in range(0,8):
	line = str(p.stdout.readline())[4:]
	for match in re.finditer("[0-9a-f]+", line):
		adresses.append(int(match.group(0), 16))
		chips.append(ADUCv2p1(int(match.group(0), 16),True))
print('Found boards', adresses, chips)
eel.renderUI(state)
eel.sleep(5)
update_state()
eel.renderUI(state)
	
	


i = 0
while(True):
	#eel.renderUI(state) # TO DO
	save_waveform() # TO DO
	eel.sleep(1/state["pi_freq"])
	if(i%10==0):
		eel.renderUI()
	if(i%100==0):
		clean_old_files("data/waveforms/", 1000)
	i += 1

	
	

print(state)


