#!/usr/bin/env python3

#imports
import os
# import platform
import sys
import time
from pathlib import Path

sys.path.append(os.path.join(os.path.dirname(__file__), "lib"))

from ctypes import *
from math import log10 as log
from statistics import mean

import matplotlib
import numpy as np
import math
import pandas as pd
# import rseriesopc as rs
from scipy.optimize import curve_fit
from scipy.stats import linregress as linreg
from matplotlib import pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.backends.backend_qt5agg import \
    NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure

matplotlib.use("Qt5Agg")
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from lib.windows import HLA_f_win as form_window

# import globals
# import qy_window
from lib.avaspec import *

# from tqdm import tqdm
# print(__file__.split("\\")[-1].split(".")[0])
# head, tail = os.path.split(__file__)
# print(head, tail)

# time.sleep(2)

cwd = os.getcwd()

# if os.path.isfile(cwd+"/avaspecx64.dll"):
#     # print("You are in the right directory!")
#     os.add_dll_directory(cwd)
#     pass
# else:
#     print("You are not in the directory with avaspecx64.dll")
#     raise FileNotFoundError

# time.sleep(1)


# lib = cdll.LoadLibrary("C:\Program Files\IVI Foundation\VISA\Win64\Bin\TLUP_64.dll")

class MainWindow(QMainWindow, form_window.Ui_MainWindow):
    k_b = 1.38e23
    h = 6.626e34
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.setupUi(self)
        self.setWindowTitle("Half-life Analysis Program")
        self.setWindowIcon(QIcon('lib/hla.png'))
#       self.OpenCommBtn.clicked.connect(self.on_OpenCommBtn_clicked)
#       do not use explicit connect together with the on_ notation, or you will get
#       two signals instead of one!
        self.fit_fig = MplCanvas()
        toolbar = NavigationToolbar(self.fit_fig, self)
        self.fitFigLayout.addWidget(self.fit_fig)
        self.fitFigLayout.addWidget(toolbar)
        self.kin_fig = MplCanvas()
        toolbar = NavigationToolbar(self.kin_fig, self)
        self.kinFigLayout.addWidget(self.kin_fig)
        self.kinFigLayout.addWidget(toolbar)
        self.arrh_fig = MplCanvas()
        toolbar = NavigationToolbar(self.arrh_fig, self)
        self.arrhFigLayout.addWidget(self.arrh_fig)
        self.arrhFigLayout.addWidget(toolbar)
        self.spec_fig = MplCanvas()
        toolbar = NavigationToolbar(self.spec_fig, self)
        self.specFigLayout.addWidget(self.spec_fig)
        self.specFigLayout.addWidget(toolbar)
        self.time_fig = MplCanvas()
        toolbar = NavigationToolbar(self.time_fig, self)
        self.timeFigLayout.addWidget(self.time_fig)
        self.timeFigLayout.addWidget(toolbar)
        #Setup the table for analysis
        self.analysisTable.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
        self.analysisTable.resizeColumnsToContents()
        self.fileName = None

    def closeEvent(self, event):

        quit_msg = "Are you sure you want to exit the program?"
        reply = QMessageBox.question(self, 'Warning', 
                         quit_msg, QMessageBox.Yes, QMessageBox.No)

        if reply == QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()


    ##############################################################################################################################

    # GENERAL UI



    @pyqtSlot() #Print time and message
    def print_to_message_box(self, text):
            t = time.localtime()
            current_time = time.strftime("%H:%M:%S", t)
            self.logTextEdit.appendPlainText(f"{current_time}  {text}")
            return

    @pyqtSlot()
    def on_runCalcBtn_clicked(self):
        ### Get calculation parameters from the fields and check if they are viable
        self.check_file()
        if self.good_file == False:
            self.print_to_message_box("Analysis cancelled due to file.")
            return
        ks = self.run_HL_calc()
        return ks
    
    @pyqtSlot()
    def on_runSimBtn_clicked(self):
        # Make the simulation prediction and show the plots.

        return
    
    @pyqtSlot()
    def check_file(self):
        self.good_file = True
        try:
            check = pd.read_csv(self.fileName, sep=' ', header = None,skiprows=2,low_memory=False)
            
        except:
            print("File could not be read.")
            self.good_file = False
            return
        check1 = check[pd.to_numeric(check.iloc[:,0], errors='coerce').notnull()]
        if np.min(check1.iloc[1,2:]) < 0:
            qm = QMessageBox
            response = qm.question(self,'', "Some values in the spectrum are below 0. Do you want to continue?", qm.Yes | qm.No)
            if response == qm.Yes:
                self.good_file = True
            elif response == qm.No:
                self.good_file = False
        return


    def check_params(self):
        self.good_params = True
        
        if self.good_params == True:
            # self.print_to_message_box("Parameters are looking good. Proceeding calculation.")
            pass
        return
    
    @pyqtSlot()
    def on_loadLEDBtn_clicked(self):
        qm = QMessageBox

        
        
        return

    @pyqtSlot()
    def get_params(self):
        params = pd.Series(dtype=object)
        params.qy = float(self.qyLineEdit.value())
        params.extinc = float(self.extincLineEdit.value())
        params.extincwl = float(self.extincwlLineEdit.value())
        params.conc = float(self.concLineEdit.value())
        params.flux = float(self.fluxLineEdit.text())
        params.fluxwl = float(self.fluxwlLineEdit.text())
        params.analysiswl = float(self.analysiswlLineEdit.text())
        params.abswl = float(self.abswlLineEdit.text())
        params.volume = float(self.volLineEdit.text())
        params.zeropoint = float(self.zeroLineEdit.text())
        return params
    
    @pyqtSlot()
    def run_QY_calc(self,params): #deprecated
        calc = QY_analysis()
        if self.qyRadio.isChecked():
            calc.qy = params.qy
        else:
            calc.qy = None
        if self.extincRadio.isChecked():
            calc.extinc = params.extinc
            calc.extincwl = params.extincwl
        if self.concRadio.isChecked():
            calc.start_conc = params.conc
        if self.fluxRadio.isChecked():
            calc.flux = params.flux
        else:
            calc.flux = None
        calc.file_name = self.fileName
        calc.plot = False
        calc.zero_wl = params.zeropoint
        calc.V = params.volume
        calc.LED_current = 300
        calc.LED_wl = params.abswl
        calc.qy_wl = params.analysiswl
        calc.num_points = self.analysisNumPoints.value()
        if self.extincRadio.isChecked():
            qys,t_list = calc.calculate_QY(calc_conc = True)
        else:
            qys,t_list = calc.calculate_QY()
        
        self.Plot_new_spectrum([t_list,calc.data_wl],[calc.nbd_conc_imag(t_list,calc.qy,qys[0],calc.c_1.real,calc.c_1.imag),calc.data_i],)
        
        self.print_to_message_box([calc.data_wl.to_list(),calc.data_i])
        
        return qys
    
    @pyqtSlot()
    def Plot_new_spectrum_old(self, x,y, draw = True,start_x = 0,end_x = -1):
        self.fit_fig.axes.clear()
        if len(x)>1:
            self.fit_fig.axes.plot(x[start_x:end_x], y[start_x:end_x],label = "Data")
            # self.fit_fig.axes.plot(x[1], y[1],label = "Data")
        else:
            self.fit_fig.axes.plot(x, y,label = "Fit")

        self.fit_fig.axes.set_xlabel("Irradiation time (s)")
        self.fit_fig.axes.set_ylabel(f"Absorption  @ {self.analysiswlLineEdit.value()} nm")
        self.fit_fig.axes.legend()
        # self.fit_fig.axes.tight_layout()
        if draw == True:
            self.fit_fig.draw()
        else:
            pass

    @pyqtSlot()
    def Plot_new_spectrum(self, x,y,func, draw = True,start_x = 0,end_x = -1,dots = False,clear = True,kin=False):
        if self.clearGraphCheck.isChecked():
            clear = False
        if clear:
            func.axes.clear()
        else:
            pass
        if kin == True:
            func.axes.plot(x[1],y[1],label="Fit")
            func.axes.scatter(x[0],y[0],label="Data")
            if draw == True:
                func.draw()
            return
        if all(isinstance(item,list) for item in x):
            # print("All instances were lists")
            
            if dots:
                func.axes.plot(np.array(x[1])*1000, y[1],label = "Fit",color="red")
                func.axes.plot(np.array(x[0])*1000, y[0], label = "Data",marker="x",linestyle="None",color="black")
                func.axes.set_xlabel("1/T x $10^{-3}$ (1/K)")
                func.axes.set_ylabel("ln(k)")
                # func.xaxis.
                func.axes.legend()
                func.figure.tight_layout()
                func.draw()
                return
            else:
                func.axes.plot(x[0][start_x:end_x], y[0][start_x:end_x],label = "Data",marker="x",color="black",linestyle="None")
                func.axes.plot(x[1], y[1],label = "Fit",color="red")
            
        else:
            # func.axes.plot(x[start_x:end_x], y[start_x:end_x],label = "Data")
            if dots == True:
                func.axes.plot(x, y,label = "Fit")
                func.axes.scatter(x, y,label="Data")
            else:
                func.axes.plot(x[start_x:end_x], y[start_x:end_x],label = "Data")

        func.axes.set_xlabel("Time (s)")
        func.axes.set_ylabel(f"Absorption  @ {self.analysiswlLineEdit.value()} nm") 
        func.axes.legend()
        
        # self.fit_fig.axes.tight_layout()
        if draw == True:
            func.draw()
        else:
            pass

    @pyqtSlot()
    def on_getFileBtn_clicked(self):
        self.fileName = self.openFileNameDialog("Choose file containing UV-VIS data from the QY experiment...")
        self.chosenLabel.setText("File chosen: "+ self.fileName.split("/")[-1])
        self.chosenLabel.setToolTip(self.fileName)
        return 

    @pyqtSlot()
    def openFileNameDialog(self,windowTitle):
        options = QFileDialog.Options()
        # options |= QFileDialog.DontUseNativeDialog
        fileName, _ = QFileDialog.getOpenFileName(self,windowTitle, "","All Files (*);;Python Files (*.py)", options=options)
        if fileName:
            print(fileName)
        return fileName

    
    @pyqtSlot()
    def on_runSimBtn_clicked(self): #deprecated
        ### RUN LINEAR PART FIRST?
        # conc = start_conc - (qy*I*t)/(V*N_A)
        ### THIS IS NON LINEAR PART
        sim = QY_analysis()
        self.sim_time = int(self.simTimeLineEdit.text())
        t_list = np.arange(0,self.sim_time*2)*0.5
        self.b = float(self.extincLineEdit.text())
        sim.b = self.b
        sim.b_ex = self.b
        sim.num_points = self.analysisNumPoints.value()
        s = float(self.concLineEdit.text())
        n=1
        print(self.b*s)
        self.c_1 = (np.log(1 - (np.power(10,(self.b * s)) + 0j )) + 2j * np.pi * n)/(self.b * np.log(10))
        flux = float(self.fluxLineEdit.text())
        qy = float(self.qyLineEdit.text())
        plt.plot(t_list,sim.nbd_conc_imag(t_list,qy,flux,self.c_1.real,self.c_1.imag)/self.b)
        plt.ylim(bottom=0)
        plt.show()
        return

    @pyqtSlot()
    def on_timeEvoBtn_clicked(self):
        file = self.fileName
        try:
            calc = HL_analysis()
            calc.data_path = file
            calc.analysis_wl = int(self.analysiswlLineEdit.value())
            calc.corr = 0
            calc.zero_wl = int(self.zeroLineEdit.value())
            calc.start_point = int(self.corrLineEdit.value())+1
            if self.accumTimeCheck.isChecked():
                calc.time_correction = self.accumTimeSpin.value()
            calc.calculate_HL()
            init_abs = float(self.initAbsLine.value())
            if init_abs == 0:
                corr_abs = [x for x in calc.abs_list]
                new_abs = [(max(corr_abs)-x) for x in corr_abs]
            else:
                corr_abs = [x for x in calc.abs_list]
                new_abs = [init_abs - x for x in corr_abs]
            self.Plot_new_spectrum(calc.time_list,calc.abs_list,self.time_fig,start_x = int(self.corrLineEdit.value()),end_x = int(self.endLineEdit.value()))
            for i in range(len(calc.first_abs)):
                if i == 0:
                    self.Plot_new_spectrum(calc.first_wl,calc.first_abs[i],self.spec_fig,start_x=150,end_x=600,clear=True,typ="spec")
                else:
                    self.Plot_new_spectrum(calc.first_wl,calc.first_abs[i],self.spec_fig,start_x=150,end_x=600,clear=False,typ="spec")
            # self.Plot_new_spectrum(calc.first_wl,calc.first_abs,self.spec_fig,start_x=0,end_x=500)
            self.data_x = np.array(calc.time_list)
            self.data_y = np.array(calc.abs_list)
        except:
            self.print_to_message_box("Calculation went wrong. Please check you parameters. Maybe the start and end points you chose are not valid.")
        return


    def run_HL_calc(self,params=None):
        file = self.fileName
        calc = HL_analysis()
        calc.data_path = file
        calc.analysis_wl = int(self.analysiswlLineEdit.value())
        calc.zero_wl = int(self.zeroLineEdit.value())
        calc.corr = 0
        calc.start_point = int(self.corrLineEdit.value())+1
        if self.accumTimeCheck.isChecked():
            calc.time_correction = self.accumTimeSpin.value()
        calc.calculate_HL()
        new_time = []
        new_abs = []
        start_point = int(self.corrLineEdit.value())
        end_point = int(self.endLineEdit.value())
        

        corr_abs = [x-min(calc.abs_list) for x in calc.abs_list]
        for i in range(len(corr_abs)):
            if corr_abs[i]!=0:
                new_time.append(calc.time_list.to_list()[i])
                new_abs.append(max(corr_abs)-corr_abs[i])
                # new_abs.append(max(calc.abs_list.to_list())-calc.abs_list.to_list()[i]-min(calc.abs_list.to_list()))
        
        #m,b = np.polyfit(new_time[start_point:end_point],[np.log(x+0.000001) for x in new_abs][start_point:end_point],1)
        if self.expRadio.isChecked() == True:
            try:
                # m,b,r2,p,five = linreg(new_time[start_point:end_point],[np.log(x+0.000001) for x in new_abs][start_point:end_point],)
                x_list = np.linspace(new_time[start_point],new_time[end_point])
                # self.Plot_new_spectrum([new_time,x_list.tolist()],[[np.log(x+0.000001) for x in new_abs],[i*m+b for i in x_list]],self.fit_fig,start_x = start_point,end_x=end_point)
                p0=[3.72E-04]
                bounds=([1e-6],[1e-1])
                new_time2 = [x-new_time[start_point] for x in new_time]
                self.b = float(self.bValueLine.value())
                self.m = new_abs[start_point]-self.b
                popt, pcov = curve_fit(self.mono_exp,new_time2[start_point:end_point],new_abs[start_point:end_point],p0=p0,bounds=bounds) 
                self.print_to_message_box(f"Rate constant at the given temperature {self.tempSpinBox.value()} degrees is: {popt[0]:.3e}\nThis corresponds to a half-life of {round((np.log(2)/popt[0])/60,2)} minutes or {round((np.log(2)/popt[0])/60,2)/(24*60)} days")
                self.print_to_message_box(f"Std. dev or Error is {np.sqrt(np.diag(pcov))}")
                self.Plot_new_spectrum([new_time2[start_point:end_point],new_time2[start_point:end_point]],[[x for x in new_abs[start_point:end_point]],[self.m * np.exp(-popt[0] * x)+self.b for x in new_time2[start_point:end_point]]],self.fit_fig)
                # self.print_to_message_box(f"The rate constant obtained is: {-m:.3e}\n At the temperature given {self.tempSpinBox.value()}C this would correspond to a half-time of {-round((np.log(2)/m)/60,2)} minutes.")
            except:
                self.print_to_message_box(f"Something went wrong with the fitting. Check your hyperparameters.")
        elif self.linRadio.isChecked() == True:
            try: 
                ln_abs = [np.log(x) if x > 0 else 0 for x in new_abs]
                new_time2 = [x-new_time[start_point] for x in new_time]
                m,b,r2,p,five = linreg(new_time2[start_point:end_point],ln_abs[start_point:end_point])

                self.print_to_message_box(f"Rate is {m}")

                self.Plot_new_spectrum([new_time2[start_point:end_point],new_time2[start_point:end_point]],[[x for x in ln_abs[start_point:end_point]],[m * x + b for x in new_time2[start_point:end_point]]],self.fit_fig)
            except:
                self.print_to_message_box(f"Something went wrong with the fitting. Check your hyperparameters.")
            
            pass
        elif self.expincRadio.isChecked() == True:
            try:
                x_list = [x - calc.time_list.to_list()[start_point] for x in calc.time_list.to_list()[start_point:end_point]]
                inc_abs_list = [x for x in calc.abs_list][start_point:end_point]
                b0 = inc_abs_list[0]
                p0 = ([20000,1,0.6])
                bounds = ([1,0.003,-0.5],[10000000,1.5,1.5])
                popt, pcov = curve_fit(self.mono_exp_inc,x_list,inc_abs_list,p0=p0,bounds=bounds)
                self.print_to_message_box(f"Rate constant at the given temperature {self.tempSpinBox.value()} degrees is: {popt[0]:.3e}\nThis corresponds to a half-life of {round((np.log(2)/popt[0])/60,2)} minutes or {round((np.log(2)/popt[0])/60,2)/(24*60)} days")
                self.print_to_message_box(f"Rate is {1/popt[0]:.4e}")
                # print(popt)
                self.Plot_new_spectrum([x_list,x_list],[inc_abs_list,[popt[2]-popt[1]*np.exp(-t/popt[0]) for t in x_list]],self.fit_fig)
            
            except:
                self.print_to_message_box(f"Something went wrong with the fitting. Check your hyperparameters.")
        
        return
    
    def mono_exp(self,t,tau):
        return self.m * np.exp(-tau * t) + self.b
    
    def mono_exp_inc(self,t,tau,m,b):
        return - m * np.exp(-t/tau) + b 
    
    @pyqtSlot()
    def on_calcKinBtn_clicked(self):
        T_list = []
        k_list = []
        try:
            for i in range(6):
                if self.analysisTable.item(i,2).checkState() == Qt.Checked:
                    T_list.append(float(self.analysisTable.item(i,0).text())+273.15)
                    k_list.append(float(self.analysisTable.item(i,1).text()))   
                else:
                    pass

            # print(T_list)
            # print(k_list) 
        except:
            self.print_to_message_box("Could not calculate kinetics. Check your values again.")
            return
        try:
            T1_list = [1/T for T in T_list]
            lnk_list = [np.log(k) for k in k_list]
            lnkT_list = [np.log(k_list[i]/T_list[i]) for i in range(len(T_list))]

            arrh = linreg(T1_list,lnk_list)

            eyring = linreg(T1_list,lnkT_list)

            A = np.exp(arrh[1])  # 1/s
            Ea = -arrh[0]*8.3145/1000  #kJ/mol

            DeltaH = -eyring[0]*8.3145/1000
            DeltaS = (eyring[1]-23.75997781)*8.3145

            halflife_roomtemp = (np.log(2)/(np.exp(arrh[1] + arrh[0]*(1/298.15))))/(60*60*24)

            self.Plot_new_spectrum([T1_list,T1_list],[lnk_list,[x*arrh[0] + arrh[1] for x in T1_list]],self.arrh_fig,dots=True)
            self.Plot_new_spectrum([T1_list,T1_list],[lnkT_list,[x*eyring[0] + eyring[1] for x in T1_list]],self.kin_fig,dots=True)
            

            self.print_to_message_box(f"_____________________\nKinetics analysis results:\nA = {A:.2e} 1/s\nActivation energy = {Ea:.3f} kJ/mol\nDeltaH = {DeltaH:3f} kJ/mol\nDeltaS = {DeltaS:.3} J/mol·K\nSlopes: {arrh[0]:.8}, {eyring[0]:.8}\nInterceptions: {arrh[1]:.8}, {eyring[1]:.8}\nR2values: {arrh[2]:.8}, {eyring[2]:.8}\np-values: {arrh[3]:.8}, {eyring[3]:.8}\nStandard errors: {arrh[4]:.8}, {eyring[4]:.8}\nThe half-life at 25 C is {halflife_roomtemp:.3f} days\nOr in seconds: {halflife_roomtemp*24*60*60}\n^^^^^^^^^^^^^^^^^^^^  	")
        except:
            self.print_to_message_box("Error in calculation of kinetics.")
        return
    
    @pyqtSlot()
    def on_exportBtn_clicked(self):
        options = QFileDialog.Options()
        fileName, _ = QFileDialog.getSaveFileName(self,"QFileDialog.getSaveFileName()","","Comma-separated files (*.csv)", options=options)
        if fileName:
            self.print_to_message_box(f"Exporting plot data to: {fileName}")
        else:
            self.print_to_message_box("No filename was selected and data has not been exported.")
            return
        try:
            df = pd.DataFrame({"time (s)":self.data_x,"abs":self.data_y})
            df.to_csv(fileName,index=False)
        except:
            self.print_to_message_box("Remember to generate some data before you export it.")
        # with open(fileName,"w") as f:
        #     f.write(self.data_x,self.data_y)
        return


class QY_analysis:
    def __init__(self):
        print("Please setup all reaction parameters for the calculation. The parameters can be found with .help()")
        #Below is the standard data for the KMP1
        self.file_name = ""
        self.LED_wl = 365 #Wavelength of the LED for irradiation
        self.qy_wl = 340 #Wavelength for the absorption peak of NBD for analysis
        self.start_conc = 9.5426e-06 #Starting concenctration of experiments performed on 21Mar2023
        self.qy = None #0.61 is the QY calculated for KMP1
        self.flux = None #Standard flux set to None, for calibration of Flux
        self.V = 80e-6 #The volume of the flow cell
        self.LED_current = 300 #The current of the LED
        self.N_A = 6.022e+23 #Avogadros number, it's required so don't question it.
        self.corr = 0
        self.zero_wl = 400
        self.num_points = 15
        return
    
    def calculate_QY(self,calc_conc = False, start_x = 0, end_x = -1):
        #Some constants
        
        #First check if the necessary parameters are set
        self.get_current_params()
#         cont = input("These are the current parameters. Are you sure you want to continue? y/n: ")
        cont = "y"
        if cont.lower() != "y":
            print("Cancelling calculation.")
            return
        data = pd.read_csv(self.file_name, sep=' ', header = None,low_memory=False)
        self.zero_col = np.argmin([abs(x-self.zero_wl) for x in data.iloc[0,:]])
        if self.corr != 0:
            corr = self.corr
        else:
            corr = -np.mean(data.iloc[1,data.iloc[0,:].round(0).to_list().index(self.zero_wl)-5:data.iloc[0,:].round(0).to_list().index(self.zero_wl)+5])
        ex_col = data.iloc[0,:].round(0).to_list().index(self.LED_wl)
        wl_col = data.iloc[0,:].round(0).to_list().index(self.qy_wl)
        # plt.plot(data.iloc[0,:],data.iloc[1,:]+corr)
        # plt.show()
        data_wl = data.iloc[1:,0]#[start_x:end_x]
        self.data_wl = data_wl
        data_wl = data_wl - data.iloc[1,0] #correction so it always starts at 0
        data_i = [num + corr for num in data.iloc[1:,wl_col]]
        data_i = (data.iloc[1:,wl_col]-data.iloc[1:,self.zero_col]).to_list()[self.start_point:self.end_point]
        self.data_i = data_i
        data_ex = [num + corr for num in data.iloc[1:,ex_col]]
        data_ex = (data.iloc[1:,ex_col]-data.iloc[1:,self.zero_col]).to_list()[self.start_point:self.end_point]
        data_t0 = data.iloc[1,1:]
        if calc_conc:
            extinc_col = data.iloc[0,:].round(0).to_list().index(self.extincwl)
            self.start_conc = (data.iloc[1,extinc_col] + corr)/self.extinc
        else:
            pass

        self.b = data_i[0] /self.start_conc
        self.b_ex = data_ex[0]/self.start_conc
        print(self.b,self.b_ex)
        remove_qc = []
        data_c = []
        for dot in range(len(data_i)):
            diff = (1-(data_i[dot]/data_i[0]))*data_i[-1]
            remove_qc.append(diff)
            data_c.append(data_i[dot]-diff)
        data_i = data_c

        self.data_ex = data_ex
        
#         plt.plot(data_wl,data_i)
#         plt.plot(data.iloc[0,1:],data.iloc[1,1:]+corr)
#         plt.xlim(right=50)
#         plt.ylim(bottom=0)
        num_points = self.num_points
        for i in range(15):
            params = self.run_qy_calc(data_wl[:i],data_c[:i],self.start_conc)

        params = self.run_qy_calc(data_wl[:num_points],data_c[:num_points],self.start_conc)
        x=np.linspace(0,data_wl[len(data_wl)],400)
        if self.plot == True:
            self.fit_fig = MplCanvas()
            toolbar = NavigationToolbar(self.Spectrum_figure, self)
            self.fitFigLayout.addWidget(self.fit_fig)
            self.fitFigLayout.addWidget(toolbar)
            plt.plot(x,self.nbd_conc_imag(x,self.qy,params[0],self.c_1.real,self.c_1.imag),label="Data")
            plt.legend()
            plt.xlabel("Time (s)")
            plt.ylabel("Absorption")
            plt.scatter(data_wl,data_c,alpha=0.5)
            plt.show()
        return params , x
                  
        
    def help(self):
        print(f"The required parameters are listed below. To see the current parameters run .get_current_params()")
        return 
    
    def get_current_params(self):
        print(f"Current parameters are:\nData file: {self.file_name}\nIrradiation wavelength: {self.LED_wl}\n"+
              f"Measurement wavelength: {self.qy_wl} nm\nStarting concenctration: {self.start_conc} M\n"+
              f"Calculated Quantum Yield: {self.qy}\nCalculated Photon Flux: {self.flux} 1/s\n"+
              f"Flow cell volume: {self.V} l\nLED current: {self.LED_current}")
        return
    
    def nbd_conc_imag(self,t,qy,I,cr,ci):
        a = (qy*I)/(self.V*self.N_A)
        return (0.434294*np.log(1-(2.71828**(2.30259*self.b*complex(cr,ci)-2.30259*a*self.b_ex*t))))
    
    def helper_qy(self,x,qy):
        return self.nbd_conc_imag(x,qy,self.I,self.c_1.real,self.c_1.imag)
    
    def helper_flux(self,x,I):
        return self.nbd_conc_imag(x,self.qy,I,self.c_1.real,self.c_1.imag)

    def run_qy_calc(self,x_vals,y_vals,start_conc):
        old_x_vals = x_vals
        old_y_vals = y_vals
#         x_vals = old_x_vals[:10]
#         y_vals = old_y_vals[:10]
        if len(x_vals)>1:
            self.b = y_vals[0] /start_conc
            s = self.start_conc
            n = 1
            self.c_1 = (np.log(1 - (10**(self.b * s) + 0j )) + 2j * np.pi * n)/(self.b * np.log(10))
    #         popt,pcov = scipy.optimize.curve_fit(nbd_conc_imag,x_vals,y_vals,bounds=([0,1e12,1.1*c_1.real,0.9*c_1.imag],[1,1e16,0.9*c_1.real,c_1.imag*1.1]),p0=[0.5,6e13,c_1.real,c_1.imag],check_finite=False)
            popt,pcov = curve_fit(self.helper_flux,x_vals,y_vals,p0=[10e14],bounds=([10e10],[10e20]),check_finite=False)
        
        
            print(f"PHOTON FLUX PREDICTED TO BE: {popt[0]/self.LED_current:.2e} 1/s @ BASED ON {len(x_vals)} DATA POINTS. b is: {self.b}")
            return popt
        return "Not enough datapoints..."
    


    def run_qy_sim(self):
        ### Get data ready for the plot
        return



class HL_analysis:
    #Constants
    k_b = 1.38e23
    h = 6.626e34
    data_path = None
    analysis_wl = 340
    corr = 0
    zero_wl = 400
    start_point = 1
    end_point = 10
    time_correction = 0
    def __init__(self): #Function to run at start
        return
    


    def get_file(self):
        
        return
    

    def read_data(self):
        time_list = None
        abs_list = None
        try:
            df = pd.read_csv(self.data_path,header=None,skiprows=2,sep=' ',low_memory=False)
            df[1] = df[1].map(lambda x: 1 if x == "True" else 0)
            # print(df.head())
            abs_col = df.iloc[0,:].astype(float).round(0).to_list().index(self.analysis_wl)
            abs_col = np.argmin([abs(x-self.analysis_wl) for x in df.iloc[0,:]])
        except:
            df = pd.read_csv(self.data_path,header=None,skiprows=2,sep=',',low_memory=False)
            df[1] = df[1].map(lambda x: 1 if x == "True" else 0)
            
        # WRITE IF THERE IS TEXT IN THE FIRST LINE THEN USE SKIPROWS=2
        self.zero_ind = np.argmin([abs(x-self.zero_wl) for x in df.iloc[0,:]])
        if self.corr != 0:
            corr = self.corr
        else:
            corr = -np.mean(df.iloc[1,self.zero_ind-5:self.zero_ind+5])
            # corr = -np.mean(df.iloc[1,df.iloc[0,:].round(0).to_list().index(self.zero_wl)-5:df.iloc[0,:].round(0).to_list().index(self.zero_wl)+5])
            corr_list = df.iloc[1:,self.zero_ind]
        print(f"corr is {corr}")
        time_list = df.iloc[1:,0]
        # abs_col = df.iloc[0,:].astype(float).round(0).to_list().index(self.analysis_wl)
        abs_col = np.argmin([abs(x-self.analysis_wl) for x in df.iloc[0,:]])
        abs_list = df.iloc[1:,abs_col]
        corr_abs = abs_list-corr_list
        self.first_wl = df.iloc[0,2:].to_list()
        self.first_abs = []
        # for spec_point in np.linspace(self.start_point,self.end_point,3,dtype=int):
        #     print(spec_point)
        #     self.first_abs.append(np.array(df.iloc[spec_point,2:].to_list())-df.iloc[:,self.zero_ind].to_list()[spec_point])
        # self.first_abs = np.array(df.iloc[self.start_point,2:].to_list())+corr
        return time_list, corr_abs
    
    def calculate_HL(self):
        self.time_list,self.abs_list = self.read_data()
        try:
            correction_list = np.arange(len(self.time_list)) * self.time_correction
            print(correction_list)
            print("correction list created")
            self.time_list_corrected = self.time_list + correction_list
        except:
            print("could not correct list")
            return
        self.time_list = self.time_list_corrected
        return
    

        


class Worker(QObject):
    finished = pyqtSignal()
    func = None
    def run(self):
        self.func()
        self.finished.emit()
        return

class MplCanvas(FigureCanvasQTAgg):

    def __init__(self, parent=None, width=4, height=4, dpi=100):
        fig = plt.figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)

def main():
    app = QApplication(sys.argv)
    app.lastWindowClosed.connect(app.quit)
    app.setApplicationName("Half-life Calculation Program")
    form = MainWindow()
    form.show()
    # form.raise_()
    app.exec_()

if __name__ == "__main__":
    main()
