# -*- coding: utf-8 -*-
"""
Created on Wed Jan  8 10:42:20 2020

@author: M11344
"""
import tkinter as tk
from tkinter import ttk
from tkinter import *
import tkinter.font as font
from tkinter import filedialog as fd
from textwrap import wrap
from lectureMCC_Pandas import LectureMccCuve
from lecture_Dicom import Lecture_Dicom
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,NavigationToolbar2Tk)
import re
import numpy as np
from echantillonnage_DoseMesure import EchantillonnageDoseMesure
from calculGamma import CalculGamma
from matplotlib.widgets import Cursor

class SnappingCursor:
    """
    A cross hair cursor that snaps to the data point of a line, which is
    closest to the *x* position of the cursor.

    For simplicity, this assumes that *x* values of the data are sorted.
    """

    def __init__(self, ax, line):
        self.ax = ax
        self.horizontal_line = ax.axhline(color='k', lw=0.8, ls='--')
        self.vertical_line = ax.axvline(color='k', lw=0.8, ls='--')
        self.x, self.y = line.get_data()
        self._last_index = None
        # text location in axes coords
        self.text = ax.text(0.3, 0.9, '', transform=ax.transAxes)

    def set_cross_hair_visible(self, visible):
        need_redraw = self.horizontal_line.get_visible() != visible
        self.horizontal_line.set_visible(visible)
        self.vertical_line.set_visible(visible)
        self.text.set_visible(visible)
        return need_redraw

    def on_mouse_move(self, event):
        if not event.inaxes:
            self._last_index = None
            need_redraw = self.set_cross_hair_visible(False)
            if need_redraw:
                self.ax.figure.canvas.draw()
        else:
            self.set_cross_hair_visible(True)
            x, y = event.xdata, event.ydata
            index = min(np.searchsorted(self.x, x), len(self.x) - 1)
            if index == self._last_index:
                return  # still on the same data point. Nothing to do.
            self._last_index = index
            x = self.x[index]
            y = self.y[index]
            # update the line positions
            self.horizontal_line.set_ydata(y)
            self.vertical_line.set_xdata(x)
            self.text.set_text('x=%1.2f, y=%1.2f' % (x, y))
            self.ax.figure.canvas.draw()

class SelectFiles(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)

        self.offAxisInPlane = 0
        self.field_size_x = None
        self.field_size_y = None
        self.depth = None
        self.curve_type = None
        self.energie_mcc = None
        self.mppg = None
        self.dsp = None
        self.ssd = None
        self.energie = None

        self.rtdose_a_garder = None
        self.mcc_a_garder = None
        self.mccDejaTrace = False
        self.RTDoseDejaTrace = False

        self.mccSelected = None
        self.dicomSelected = None

        self.ax_1 = None
        self.echantillonnageMCC = None
        self.objPlan = None
        self.listePDD = None
        self.filenameRTDose = None
        self.clear = False
        self.mcc_item = ' '

        ### définition des frames
        self.frameDicomGeneral = Frame(self, highlightbackground="gray", highlightthickness=1)
        self.frameDicomGeneral.grid(row=0, column=0)

        self.frameMCCGeneral = Frame(self, highlightbackground="gray", highlightthickness=1)
        self.frameMCCGeneral.grid(row=0, column=1, sticky='n', padx=100)

        self.frameTraceMatplotlib = Frame(self, highlightbackground="gray", highlightthickness=1)
        self.frameTraceMatplotlib.grid(row=1, column=0, columnspan=2, sticky='n', pady=10)

        self.frameTraceGamma = Frame(self, highlightbackground="gray", highlightthickness=1)
        self.frameTraceGamma.grid(row=2, column=0, columnspan=2, sticky='n')

        ################################################################################################################################################################
        ### définition du sous frame d'ouverture des fichiers dicom
        ################################################################################################################################################################
        self.sousFrameOuvertureDicom = Frame(self.frameDicomGeneral)
        self.sousFrameOuvertureDicom.grid(row=0, column=0, sticky='w', pady=10)
        ### Définition des boutons de sélection
        self.boutonLoad_RTDose = ttk.Button(self.sousFrameOuvertureDicom, text='Open Dicom dose',
                                            command=self.selectDoseFile)
        self.boutonLoad_RTDose.grid(row=0, column=0, padx=20)

        self.boutonLoad_RTPlan = ttk.Button(self.sousFrameOuvertureDicom, text='Open Dicom plan',
                                            command=self.selectPlanFile)
        self.boutonLoad_RTPlan.grid(row=0, column=1)
        ################################################################################################################################################################

        ################################################################################################################################################################
        ### Définition du sous frame pour afficher les fichiers dicom ouverts
        ################################################################################################################################################################
        self.sousFrameAffichageFichierOuvert = Frame(
            self.frameDicomGeneral)  # , highlightbackground="red", highlightthickness=2)
        self.sousFrameAffichageFichierOuvert.grid(row=1, column=0, sticky='w', pady=10)

        ### Définition des labels pour afficher quels fichiers ont été ouverts
        self.lblRTPlan_Ouvert = ttk.Label(self.sousFrameAffichageFichierOuvert, text='RTPLAN ouvert : ')
        self.lblRTDose_Ouvert = ttk.Label(self.sousFrameAffichageFichierOuvert, text='RTDose ouvert : ')

        self.lblRTPlan_Ouvert.grid(row=1, column=0)
        self.lblRTDose_Ouvert.grid(row=2, column=0)
        ################################################################################################################################################################

        ################################################################################################################################################################
        ### définition du sous frame dicom offset
        ########################################################################################################################
        self.sousFrameDicomOffset = Frame(self.frameDicomGeneral)  # , highlightbackground="red", highlightthickness=2)
        self.sousFrameDicomOffset.grid(row=3, column=0, pady=10)

        ### définition du label pour expliquer l'offset
        self.labelExplicationOffset = ttk.Label(self.sousFrameDicomOffset,
                                                text='Sélectionner une valeur Off-Axis en mm')
        self.labelExplicationOffset['font'] = font.Font(weight='bold')
        self.labelExplicationOffset.grid(row=0, column=0, columnspan=3)

        ### définition des labels pour expliquer quel axe correspond à quel offset
        self.labelOffsetGaucheDroite = ttk.Label(self.sousFrameDicomOffset, text='Gauche-Droite')
        self.labelOffsetGaucheDroite.grid(row=1, column=0)

        self.labelOffsetTetePied = ttk.Label(self.sousFrameDicomOffset, text='Tete-Pied')
        self.labelOffsetTetePied.grid(row=1, column=1)

        self.labelOffsetAntPost = ttk.Label(self.sousFrameDicomOffset, text='Ant-Post')
        self.labelOffsetAntPost.grid(row=1, column=2)

        ### Définition des entry d'offset
        self.valeurOffsetGaucheDroite = tk.IntVar()
        self.valeurOffsetGaucheDroite.set(0)
        self.entryGaucheDroite = tk.Entry(self.sousFrameDicomOffset, textvariable=self.valeurOffsetGaucheDroite,
                                          width=10)
        self.entryGaucheDroite.grid(row=2, column=0)

        self.valeurOffsetTetePied = tk.IntVar()
        self.valeurOffsetTetePied.set(0)
        self.entryTetePied = tk.Entry(self.sousFrameDicomOffset, textvariable=self.valeurOffsetTetePied, width=10)
        self.entryTetePied.grid(row=2, column=1)

        self.valeurOffsetAntPost = tk.IntVar()

        self.valeurOffsetAntPost.set(0)
        self.entryAntPost = tk.Entry(self.sousFrameDicomOffset, textvariable=self.valeurOffsetAntPost, width=10)
        self.entryAntPost.grid(row=2, column=2)
        ################################################################################################################################################################

        ################################################################################################################################################################
        ### définition du sous frame de choix de l'axe du RTDose
        ################################################################################################################################################################
        self.sousFrameAxeRTDose = Frame(self.frameDicomGeneral)  # , highlightbackground="gray", highlightthickness=1)
        self.sousFrameAxeRTDose.grid(row=4, column=0, sticky='w')
        ### Définition des checkbox pour afficher soit PDD ou profile du RTDose
        self.valeurRadioButtonPDD_Profil = tk.IntVar()
        self.radioButtonPDD = tk.Radiobutton(self.sousFrameAxeRTDose, text='PDD',
                                             variable=self.valeurRadioButtonPDD_Profil,
                                             value=1)  # , command=self.drawPDD_RTDose)
        self.radioButtonCrossPlane = tk.Radiobutton(self.sousFrameAxeRTDose, text='CrossPlane',
                                                    variable=self.valeurRadioButtonPDD_Profil,
                                                    value=2)  # , command = self.drawCrossPlane_RTDose)
        self.radioButtonInPlane = tk.Radiobutton(self.sousFrameAxeRTDose, text='InPlane',
                                                 variable=self.valeurRadioButtonPDD_Profil,
                                                 value=3)  # , command=self.drawInPlane_RTDose)
        self.radioButtonPDD.grid(row=0, column=0, ipadx=20)
        self.radioButtonCrossPlane.grid(row=0, column=1, padx=20)
        self.radioButtonInPlane.grid(row=0, column=2)
        ################################################################################################################################################################

        self.boutonLoad_MCC = ttk.Button(self.frameMCCGeneral, text='Open MCC file', command=self.selectMccFile)
        self.boutonLoad_MCC.grid(row=0, column=0, sticky='n')

        self.lblMCC_Ouvert = ttk.Label(self.frameMCCGeneral, text='MCC ouvert : ')
        self.lblMCC_Ouvert.grid(row=1, column=0, sticky='w')

        ### Combobox pour affichage des mesure du MCC
        self.combobox = ttk.Combobox(self.frameMCCGeneral, width=50)
        self.combobox.grid(row=2, column=0)

        ################################################################################################################################################################
        ### définition de la configuration du canvas de Matplotlib et des boutons d'affichage de la dose et du reset du graphique
        ################################################################################################################################################################
        ### Définition du canvas qui contiendra le tracé Matplotlib
        self.fig = Figure(figsize=(6, 6), dpi=100)
        self.canvas = FigureCanvasTkAgg(self.fig, master=self.frameTraceMatplotlib)
        #

        ### placing the canvas on the Tkinter window
        self.canvas.get_tk_widget().grid(row=0, column=0, columnspan=3)
        self.canvas.draw()
        ################################################################################################################################################################
        ### Définition du bouton d'affichage du RTDose
        self.boutonDisplayRTDose = ttk.Button(self.frameTraceMatplotlib, text='Afficher la dose',
                                              command=self.getValueOffset)
        self.boutonDisplayRTDose.grid(row=1, column=0, padx=50)

        ### Bouton pour effacer les graphs
        self.boutonClear = ttk.Button(self.frameTraceMatplotlib, text='Clear all plots', command=self.clearPlots)
        self.boutonClear.grid(row=1, column=1)
        self.clear = False

        ### définition du bouton d'affichage du gamma
        self.boutonCalculGamma = ttk.Button(self.frameTraceMatplotlib, text='Calcul Gamma', command=self.calculGamma)
        self.boutonCalculGamma.grid(row=1, column=2)

        ### définition du bouton d'enregistrement de la figure
        self.boutonCalculGamma = ttk.Button(self.frameTraceMatplotlib, text='save fig', command=self.enregistrementPlot)
        self.boutonCalculGamma.grid(row=1, column=3)
        ################################################################################################################################################################

    def calculGamma(self):
        if self.objPlan == None or self.objMCC == None:
            tk.messagebox.showerror(title=None,
                                    message="Sélectionnez d'abord un RTDose et une mesure MCC avant de calculer le gamma")
            return
        else:
            # self.figGamma.clear()
            if self.valeurRadioButtonPDD_Profil.get() == 1:
                calculGamma = CalculGamma(self.abscisseDuDataFrame,
                                          self.ordonneesDuDataFrame,
                                          self.listePDD[:, 0],
                                          self.listePDD[:, 1]
                                          )
                self.traceMatplotlibGamma(self.listePDD[:, 0], calculGamma.gamma)
                # self.traceMatplotlibGamma(self.abscisseDuDataFrame, calculGamma.valid_gamma)

            elif self.valeurRadioButtonPDD_Profil.get() == 2:
                calculGamma = CalculGamma(self.abscisseDuDataFrame,
                                          self.ordonneesDuDataFrame,
                                          self.listeCrossPlaneDose[:, 0],
                                          self.listeCrossPlaneDose[:, 1]
                                          )
                self.traceMatplotlibGamma(self.listeCrossPlaneDose[:, 0], calculGamma.gamma)
                # self.traceMatplotlibGamma(self.abscisseDuDataFrame, calculGamma.gamma)

            else:
                calculGamma = CalculGamma(self.abscisseDuDataFrame,
                                          self.ordonneesDuDataFrame,
                                          self.listeInPlaneDose[:, 0],
                                          self.listeInPlaneDose[:, 1]
                                          )
                self.traceMatplotlibGamma(self.listeInPlaneDose[:, 0], calculGamma.gamma)
                # self.traceMatplotlibGamma(self.abscisseDuDataFrame, calculGamma.valid_gamma)

    def updateEntryValue(self):
        x = self.valeurOffsetGaucheDroite.get()
        y = self.valeurOffsetTetePied.get()
        z = self.valeurOffsetAntPost.get()
        self.valeurOffsetGaucheDroite.set(x)
        self.valeurOffsetTetePied.set(y)
        self.valeurOffsetAntPost.set(z)

    def selectDoseFile(self):
        """Fonction reliée au pushbutton de sélection du dicom dose. Quand le dicom est sélectionné,
        il est lu avec pydicom dans la classe LectureDicom avec la fonction dicomread_Dose. Elle prend
        en paramètre le fichier dicom ainsi que les coordonnées d'un éventuel offset dans l'ordre
        suivant : GD-TP-AP. Le RTPLAN aura été ouvert et lu avant, de là conversion de la position de l'iso
        du repère CT dans le repère de dose avec la méthode passageRepereCTVersDose. Puis on extrait le PDD
        """

        if self.objPlan == None:
            tk.messagebox.showerror(title=None, message="Sélectionnez d'abord un RTPLAN avant  d'ouvrir un RTDose")
            return

        filetypes = [('dicom files', '.dcm')]

        if self.mppg is not None and self.energie is not None :
            initial_dir = r'C:\Users\M11142\OneDrive - Centre Oscar Lambret\Truebeam\01 Acceptance - Commissioning - ' \
                          r'Mesures\02 Relatif - Model TPS\04 TPS\02 RT PLANS\MPPG %s\%s_%s' % (self.mppg,self.mppg,
                                                                                self.energie)

        else:
            initial_dir = r'C:\Users\M11142\OneDrive - Centre Oscar Lambret\Truebeam\01 Acceptance - Commissioning - ' \
                          r'Mesures\02 Relatif - Model TPS\04 TPS\02 RT PLANS'

        print(initial_dir)
        self.filenameRTDose = fd.askopenfilename(title='Selectionner RTDose',
                                                 initialdir=initial_dir,
                                                 filetypes=filetypes)

        ### Définition du champs qui affiche le chemin du rtplan
        self.afficherCheminRTDOSE_Ouvert = tk.Label(self.sousFrameAffichageFichierOuvert, text=self.filenameRTDose)
        self.afficherCheminRTDOSE_Ouvert.grid(row=2, column=1)

    def getValueOffset(self):

        if self.valeurRadioButtonPDD_Profil.get() != 1 and self.valeurRadioButtonPDD_Profil.get() != 2 and self.valeurRadioButtonPDD_Profil.get() != 3:
            tk.messagebox.showerror(title=None, message="Sélectionnez si c'est un PDD ou un profil")

        elif self.filenameRTDose == None:
            tk.messagebox.showerror(title=None, message="Sélectionnez d abord un RTDose")

        elif self.listePDD is not None:
            self.executeLectureDicom(float(self.entryGaucheDroite.get()),
                                     float(self.entryTetePied.get()),
                                     float(self.entryAntPost.get())
                                     )

            if self.valeurRadioButtonPDD_Profil.get() == 1:
                self.preparationTraceRTDose(self.listePDD)
            elif self.valeurRadioButtonPDD_Profil.get() == 2:
                self.preparationTraceRTDose(self.listeCrossPlaneDose)
            else:
                self.preparationTraceRTDose(self.listeInPlaneDose)

        else:

            self.executeLectureDicom(float(self.entryGaucheDroite.get()),
                                     float(self.entryTetePied.get()),
                                     float(self.entryAntPost.get())
                                     )

    def executeLectureDicom(self, offsetGD, offsetTP, offsetAP):
        self.objPlan.dicomread_Dose(self.filenameRTDose, offsetGD, offsetTP, offsetAP)
        self.objPlan.passageRepereCTVersDose()
        self.listePDD = self.objPlan.pdd()
        self.listeCrossPlaneDose = self.objPlan.profilCrossPlane()
        self.listeInPlaneDose = self.objPlan.profilInPlane()

        if self.valeurRadioButtonPDD_Profil.get() == 1:
            self.preparationTraceRTDose(self.listePDD)
        elif self.valeurRadioButtonPDD_Profil.get() == 2:
            self.preparationTraceRTDose(self.listeCrossPlaneDose)
        else:
            self.preparationTraceRTDose(self.listeInPlaneDose)

    def preparationTraceRTDose(self, arrayDose):
        if self.valeurRadioButtonPDD_Profil.get() == 1:
            arrayDose[:, 1] = np.divide(arrayDose[:, 1], max(arrayDose[:, 1]))
            self.traceMatplotlibGeneral(arrayDose, 'RTDose')
        else:
            if self.echantillonnageMCC:
                x = 0
                diffArray = np.absolute(arrayDose[:, 0] - x)
                idx = diffArray.argmin()
                print('idx : ', idx, ' ', arrayDose[:, 0][idx])
                # arrayDose[:,1] = np.multiply(arrayDose[:,1], self.ordonneesDuDataFrame[np.where((self.abscisseDuDataFrame == 0))]/arrayDose[:,1][idx])
                arrayDose[:, 1] = np.divide(arrayDose[:, 1], max(arrayDose[:, 1]))

                ### j'ai mis un true pour afficher des mesures off-axis si problème d'abscissecar problème sens de lecture du RTDose
                ### Pour mesures centrées, enlever le True
                self.traceMatplotlibGeneral(arrayDose, 'RTDose')
            else:
                self.traceMatplotlibGeneral(arrayDose, 'RTDose')

    def selectPlanFile(self):
        """Fonction reliée au pushbutton de sélection du dicom plan. Quand le dicom est sélectionné,
        il est lu avec pydicom dans la classe LectureDicom avec la fonction dicomread_Plan. Elle prend
        en paramètre le fichier dicom.
        """
        filetypes = [('dicom files', '.dcm')]

        if self.mppg is not None and self.energie is not None :
            initial_dir = r'C:\Users\M11142\OneDrive - Centre Oscar Lambret\Truebeam\01 Acceptance - Commissioning - Mesures\02 Relatif - Model TPS\04 TPS\02 RT PLANS\MPPG %s\%s_%s' % (self.mppg,self.mppg, self.energie)

        else:
            initial_dir = r'C:\Users\M11142\OneDrive - Centre Oscar Lambret\Truebeam\01 Acceptance - Commissioning - Mesures\02 Relatif - Model TPS\04 TPS\02 RT PLANS'

        print(initial_dir)
        filename = fd.askopenfilename(title='Selectionner RTPlan',
                                      initialdir=initial_dir,
                                      filetypes=filetypes)

        ### Définition du champs qui affiche le chemin du rtplan
        # self.afficherCheminRTPLAN_Ouvert = tk.Label(self.sousFrameAffichageFichierOuvert, text =filename)
        # self.afficherCheminRTPLAN_Ouvert.grid(row=1, column=1)

        self.objPlan = Lecture_Dicom()
        self.objPlan.dicomread_Plan(filename)

    def selectMccFile(self):
        """Fonction reliée au pushbutton de sélection du MCC. Quand il est sélectionné,
        il est lu avec Pandas afin d'extraire toutes les mesures enregistrées (PDD ou profil)
        et tout est stocké dans un dictionnaire. On met ensuite toutes les clés du dictionnaire
        dans une combobox
        """
        filetypes = [('mcc files', '.mcc')]

        if self.mppg is not None and self.energie is not None :
            initialdir = r'C:\Users\M11142\OneDrive - Centre Oscar Lambret\Truebeam\01 Acceptance - Commissioning - ' \
                         r'Mesures\02 Relatif - Model TPS\04 TPS\01 Mesures cuve\MPPG %s' % (self.mppg)
        else:
            initialdir = r'C:\Users\M11142\OneDrive - Centre Oscar Lambret\Truebeam\01 Acceptance - Commissioning - Mesures\02 Relatif - Model TPS\04 TPS\01 Mesures cuve'

        self.filenameMCC = fd.askopenfilename(title='Selectionner MCC',
                                              initialdir=initialdir,
                                              filetypes=filetypes)

        # Utilisation d'expressions régulières pour extraire les valeurs recherchées
        mppg_match = re.search(r"/MPPG (\d+\.\d+)", self.filenameMCC)
        dsp_match = re.search(r"/DSP (\d+)", self.filenameMCC)
        energie_match = re.search(r"/(X\d+(?: FFF)?)\.mcc", self.filenameMCC)
        if energie_match is None:
            energie_match = re.search(r"/(X\d+(?: FFF)?)",  self.filenameMCC)

        if mppg_match:
            self.mppg = mppg_match.group(1)
            print("mppg =", self.mppg)
        if dsp_match:
            self.dsp = dsp_match.group(1)
            print("dsp =", self.dsp)
        if energie_match:
            self.energie_mcc = energie_match.group(1)
            self.energie = self.energie_mcc.replace("X06", "X6").replace("X6 FFF","X6FFF").replace("X06 FFF", "X6FFF").replace("X6 FFF", "X6FFF").replace("X10 FFF", "X10FFF")

            print("energie =", self.energie)

        ### Définition du champs qui affiche le chemin du mcc
        # self.afficherCheminMCC_Ouvert = tk.Label(self.frameMCCGeneral, text = self.filenameMCC)
        # self.afficherCheminMCC_Ouvert.grid(row=1, column=0, padx=80)

        ### instanciation de la classe EchantillonnageDoseMesure afin de savoir si le MCC a été ouvert
        ### cela permet de savoir comment afficher le RTDose dans la fonction selectDoseFile.
        ### Si le MCC n'a pas été ouvert, self.echantillonnageMCC vaudra None
        self.echantillonnageMCC = EchantillonnageDoseMesure()

        ### On instancie la classe LectureMccCuve afin de lire le MCC et de tout stocker dans un dictionnaire
        ### avec Pandas. La clé sera le setup de la mesure, la valeur sera les valeurs mesurées à la chambre
        self.objMCC = LectureMccCuve(self.filenameMCC)

        ### Ici on met à jour la combobox, c'est à dire qu'on affiche toutes les clés du dictionnaire
        ### donc tous les setup de mesure. Si on change de MCC, tout sera effacé et mis à jour
        self.updateCombobox(self.objMCC.listeCle)

    def updateCombobox(self, cleDictMCC):
        """Remplissage des lignes de la combobox avec les clés du dictionnaire du MCC.
        Appel de la fonction choixItemCombobox quand on sélectionne un item de la combobox
        """
        # n = tk.StringVar()
        # self.combobox = ttk.Combobox(self, width=50, textvariable=n)
        self.combobox['values'] = cleDictMCC
        # self.combobox.grid(row=1, column=0)
        self.combobox.bind('<<ComboboxSelected>>', self.choixItemCombobox)

    def choixItemCombobox(self, event):
        ### la variable self.itemSelected contient l'item de la combobox sélectionné
        self.itemSelected = self.combobox.get()

        print('a : ', self.itemSelected)

        # On récupères les propriétés de la mesure sélectionnée
        # Utilisation d'expressions régulières pour extraire les valeurs recherchées
        curve_type_match = re.search(r'(INPLANE_PROFILE|CROSSPLANE_PROFILE|PDD)', self.itemSelected)
        field_size_match = re.search(r'Field Size = (\d+\.\d+) (\d+\.\d+)', self.itemSelected)
        depth_match = re.search(r'depth = (\d+\.\d+)', self.itemSelected)
        ssd_match = re.search(r'SSD = (\d+\.\d+)', self.itemSelected)
        offAxisInPlane_match = re.search(r'offAxisInPlane = (-?\d+\.\d+)', self.itemSelected)
        collOffsetCrossPlane_match = re.search(r'collOffsetCrossPlane = (-?\d+\.\d+)', self.itemSelected)
        collOffsetInPlane_match = re.search(r'collOffsetInPlane = (-?\d+\.\d+)', self.itemSelected)

        if curve_type_match :
            self.curve_type = curve_type_match.group(1)

        if field_size_match :
            self.field_size_x = float(field_size_match.group(1))
            self.field_size_y = float(field_size_match.group(2))
        if depth_match :
            self.depth = float(depth_match.group(1))

        if ssd_match :
            self.ssd = float(ssd_match.group(1))

        if offAxisInPlane_match :
            self.offAxisInPlane = float(offAxisInPlane_match.group(1))

        # Valeur de décalage en crossplane (pour MPPG 7.2)
        try  :
            self.collOffsetCrossPlane = float(collOffsetCrossPlane_match.group(1))
        except AttributeError :
            self.collOffsetCrossPlane = 0

        # Valeur de décalage en inplane (pour MPPG 7.2)
        try:
            self.collOffsetInPlane = float(collOffsetInPlane_match.group(1))
        except AttributeError:
            self.collOffsetInPlane = 0

        print('-------------------------------------------')
        print("curve_type =", self.curve_type)
        print("Field size [Y,X] =", [self.field_size_y, self.field_size_x])
        print("depth =", self.depth)
        print("ssd =", self.ssd)
        print("offAxisInPlane =", self.offAxisInPlane)
        print("collOffsetCrossPlane =", self.collOffsetCrossPlane)
        print("collOffsetInPlane =", self.collOffsetInPlane)
        print('\n')

        # ---------------------------------------------------------------------------------------------
        # Initialisation des valeurs de décalage en G/D, Ant/Post et Tête/Pied
        # ---------------------------------------------------------------------------------------------

        # 1. Ant/Post
        self.valeurOffsetAntPost.set(1)  # Change la valeur de la case à cocher à "cochée": todo: voir si cette ligne est utile
        self.entryAntPost.delete(0, tk.END)  # Efface le contenu actuel de la zone de texte
        nouvelle_valeur = round( (self.depth+self.ssd)-1000,1)
        self.entryAntPost.insert(0,nouvelle_valeur)

        if self.curve_type == 'PDD':
            self.valeurRadioButtonPDD_Profil.set(1)

        elif self.curve_type == 'INPLANE_PROFILE':
            self.valeurRadioButtonPDD_Profil.set(3)

            # On modifie G/D
            delta1 = self.collOffsetCrossPlane*(self.depth+self.ssd)/1000 # Théorème de Thalès

            self.entryGaucheDroite.delete(0, tk.END)
            new_val_gauche_droite = delta1
            self.entryGaucheDroite.insert(0, new_val_gauche_droite)

            # on remet tete pieds à 0
            self.entryTetePied.delete(0, tk.END)
            self.entryTetePied.insert(0, 0)

        elif self.curve_type == 'CROSSPLANE_PROFILE':
            self.valeurRadioButtonPDD_Profil.set(2)

            # On modifie tete pieds
            delta2 = self.collOffsetInPlane*(self.depth+self.ssd)/1000 # Théorème de Thalès
            self.entryTetePied.delete(0, tk.END)
            new_val_tete_pied = delta2
            self.entryTetePied.insert(0, -delta2 + self.offAxisInPlane)  #ATTENTION !!!!! Pas sûr du signe - ici

            # on remet gauche droite à 0
            self.entryGaucheDroite.delete(0, tk.END)
            self.entryGaucheDroite.insert(0, 0)

        ### on exécute la fonction miseEnFormeDesDonneesDuMCC afin de mettre en forme le PDD
        ### en normalisant par le max ou pour les profils selon méthode fogliata
        self.miseEnFormeDesDonneesDuMCC()

        ### ajout de cette condition pour savoir si le dicom a déjà été ouvert
        ### Si c'est le cas, comme self.echantillonnageMCC a été créé dans la fonction
        ### selectMccFile, on exécute la fonction echantillonnage PDD afin de garder
        ### dans le PDD les mêmes abscisses que ceux du MCC, pour faire une comparaison point par point
        ### puis on trace les graps du MCC et de la dose
        ### Si dicom n'a pas été ouvert avant, on trace uniquement le MCC
        # if self.objPlan:
        #     print('le plan a deja ete ouvert')
        #     # self.fig.clear()
        #     # # pdd = self.echantillonnageMCC.echantillonnagePDD(self.listePDD, self.donneesDuDataFrame)
        #     self.traceMatplotlibMCCDansFenetre(self.abscisseDuDataFrame, self.ordonneesDuDataFrame)
        #     # self.traceMatplotlibRtdoseDansFenetre(pdd)
        # else:
        #     self.traceMatplotlibMCCDansFenetre(self.abscisseDuDataFrame, self.ordonneesDuDataFrame)

        # self.traceMatplotlibMCCDansFenetre(self.abscisseDuDataFrame, self.ordonneesDuDataFrame)
        # print(self.arrayMCC)
        self.traceMatplotlibGeneral(self.arrayMCC, 'MCC')

    def miseEnFormeDesDonneesDuMCC(self):
        """Permet de normaliser au max les PDD et les profils sur l'axe selon Fogliata"""

        ### Conversion des valeurs de la clé du dictionnaire correspondant à
        ### l'item sélectionné dans le combobox en numpy array

        self.donneesDuDataFrame = []
        self.arrayMCC = []

        self.donneesDuDataFrame = self.objMCC.dictMCC[self.itemSelected].to_numpy()

        ### On définit les valeurs en abscisse
        self.abscisseDuDataFrame = self.donneesDuDataFrame[:, 0]

        # convertir toutes les données de self.abscisseDuDataFrame en float sans utiliser la fonction np.float
        for i in range(len(self.abscisseDuDataFrame)):
            self.abscisseDuDataFrame[i] = float(self.abscisseDuDataFrame[i])

        ### On définit les ordonnées selon que ce soit un PDD ou un profil
        if 'PDD' in self.itemSelected:
            self.ordonneesDuDataFrame = self.donneesDuDataFrame[:, 1]
            for i in range(len(self.ordonneesDuDataFrame)):
                self.ordonneesDuDataFrame[i] = float(self.ordonneesDuDataFrame[i])
            self.ordonneesDuDataFrame = np.divide(self.ordonneesDuDataFrame, max(self.ordonneesDuDataFrame))
        else:
            aNorm = 95.44
            bNorm = 0.0084
            cNorm = 0.1125
            dNorm = -0.0118
            eNorm = 0.00113
            self.renormalization_Value = (aNorm + bNorm * float(self.itemSelected.split()[4]) / 10 + cNorm * float(
                self.itemSelected.split()[-1]) / 10 / (1 + dNorm * float(
                self.itemSelected.split()[4]) / 10 + eNorm * float(
                self.itemSelected.split()[-1]) / 10))

            self.ordonneesDuDataFrame = self.donneesDuDataFrame[:, 1]
            for i in range(len(self.ordonneesDuDataFrame)):
                self.ordonneesDuDataFrame[i] = float(self.ordonneesDuDataFrame[i])

            # self.ordonneesDuDataFrame = np.multiply(self.ordonneesDuDataFrame, self.renormalization_Value / self.ordonneesDuDataFrame[np.where((self.abscisseDuDataFrame == 0))])

            ### décommenter cette ligne là pour analyser les profils off axis
            self.ordonneesDuDataFrame = np.divide(self.ordonneesDuDataFrame, max(self.ordonneesDuDataFrame))

        ### On regroupe les abscisses et les ordonnées dans une numpy array à 2D
        self.donneesDuDataFrame = np.c_[self.abscisseDuDataFrame, self.ordonneesDuDataFrame]

        self.arrayMCC = np.c_[self.abscisseDuDataFrame, self.ordonneesDuDataFrame]

    def update_plot(self):

        self.fig.clear()
        self.ax_1 = self.fig.add_subplot(111)

    def clearPlots(self):
        self.fig.clear()
        self.ax_1 = self.fig.add_subplot(111)

    def draw_plot(self,arrayMCC_Ou_Dose, label, xflip=False):
        self.ax_1.set_xlabel('mm')
        self.ax_1.set_ylabel('Dose mesurée (%)')

        self.ax_1.set_ylim([0, max(arrayMCC_Ou_Dose[:, 1]) * 1.1])
        self.ax_1.grid(True)

        if self.echantillonnageMCC:
            self.ax_1.set_title('mcc path : ' + self.filenameMCC.split('/')[-1] + '\n' +
                                'mesure analysee : ' + self.itemSelected,
                                loc='center', size = 10,
                                wrap=True)
            self.ax_1.set_xlim([min(self.donneesDuDataFrame[:, 0]), max(self.donneesDuDataFrame[:, 0])])
        else:
            self.ax_1.set_xlim([min(arrayMCC_Ou_Dose[:, 0]), max(arrayMCC_Ou_Dose[:, 0])])

        ### Condition ajoutée dans le cas ces profils off-axis pour faire un flip des abscisses

        if 'RTDose' in label:
            color = 'red'
        else:
            color = 'blue'

        self.ax_1.plot(arrayMCC_Ou_Dose[:, 0], arrayMCC_Ou_Dose[:, 1], label=label, color=color)
        self.ax_1.legend(loc='best')
        self.canvas.draw()

    def traceMatplotlibGeneral(self, arrayMCC_Ou_Dose, label, xflip=False):

        if self.mccSelected == self.itemSelected:
            print("Le mcc n'a pas été modifié")

        # Si le dicom n'a pas déjà été tracé, on trace le mcc. On update le plot à chaque fois
        if label == 'MCC' and not self.RTDoseDejaTrace:
            print('Mcc sélectionné, RTDose pas encore tracé')
            self.update_plot()
            self.mcc_a_garder = arrayMCC_Ou_Dose
            self.draw_plot(arrayMCC_Ou_Dose, label, xflip)
            self.mccDejaTrace = True

        elif label == 'RTDose' and not self.mccDejaTrace:
            print('RTDose sélectionné, MCC pas encore tracé')
            self.update_plot()
            self.rtdose_a_garder = arrayMCC_Ou_Dose
            self.draw_plot(arrayMCC_Ou_Dose, label, xflip)
            self.RTDoseDejaTrace = True

        elif label == 'RTDose' and self.mccDejaTrace:
            print('RTDose sélectionné, MCC déjà tracé')
            self.update_plot()
            self.rtdose_a_garder = arrayMCC_Ou_Dose
            self.draw_plot(arrayMCC_Ou_Dose, label, xflip)
            self.draw_plot(self.mcc_a_garder, label= 'MCC',)
            self.RTDoseDejaTrace = True

        elif label == 'MCC' and self.RTDoseDejaTrace:
            print('MCC sélectionné, RTDose déjà tracé')
            self.update_plot()
            self.mcc_a_garder = arrayMCC_Ou_Dose
            self.draw_plot(arrayMCC_Ou_Dose, label, xflip)
            self.draw_plot(self.rtdose_a_garder, label= 'RTDose')
            self.mccDejaTrace = True

        elif label == 'RTDose' and self.RTDoseDejaTrace:
            print('RTDose sélectionné, RTDose déjà tracé')
            self.update_plot()
            self.rtdose_a_garder = arrayMCC_Ou_Dose
            self.draw_plot(arrayMCC_Ou_Dose, label, xflip)
            self.draw_plot(self.mcc_a_garder, label= 'MCC')
            self.RTDoseDejaTrace = True

    # def traceMatplotlibGamma(self, arrayMCC_Ou_Dose, gamma):
    #
    #     # ax_1 = self.fig.add_subplot(111)
    #     ax_2 = self.ax_1.twinx()
    #     ax_2.plot(arrayMCC_Ou_Dose, gamma, label='Gamma')
    #     ax_2.set_ylabel('Gamma mesurée (%)')
    #     # ax_2.set_xlim([min(arrayMCC_Ou_Dose), max(arrayMCC_Ou_Dose)])
    #     # ax_2.set_ylim([0, max(gamma) * 1.1])
    #     ax_2.grid(True)
    #
    #     line, = ax_2.plot(arrayMCC_Ou_Dose, gamma, 'o')
    #     snap_cursor = SnappingCursor(ax_2, line)
    #     self.fig.canvas.mpl_connect('motion_notify_event', snap_cursor.on_mouse_move)
    #     ax_2.legend()
    #     ax_2.show()
    #
    #     ### creating the Tkinter canvas containing the Matplotlib figure
    #     self.canvas.draw()
    #
    #     ### placing the canvas on the Tkinter window
    #     self.canvas.get_tk_widget().grid(row=0, column=0, columnspan=3)

    def enregistrementPlot(self):
        folder = r'C:\Users\M11142\OneDrive - Centre Oscar Lambret\Truebeam\01 Acceptance - Commissioning - Mesures\02 Relatif - Model TPS\04 TPS\02 RT PLANS\MPPG %s\%s_%s' % (self.mppg,self.mppg,self.energie)
        # folder = r'G:\CAYEZ\temp\dose'
        print(r'folder : ', folder)
        try:
            self.fig.savefig(folder + '\\' + self.filenameMCC.split('/')[-1] + '  ' + self.itemSelected + '.png', dpi=600)
        except:
            energie = self.energie+'FFF'
            folder = r'C:\Users\M11142\OneDrive - Centre Oscar Lambret\Truebeam\01 Acceptance - Commissioning - Mesures\02 Relatif - Model TPS\04 TPS\02 RT PLANS\MPPG %s\%s_%s' % (self.mppg,self.mppg,energie)
            self.fig.savefig(folder + '\\' + self.filenameMCC.split('/')[-1] + '  ' + self.itemSelected + '.png', dpi=600)


from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class BasePage:

    def __init__(self, driver):
        self.driver = driver

    def find_element(self, locator: tuple, time=10):
        return WebDriverWait(self.driver, time).until(
            EC.presence_of_element_located(locator),
            message=f'can not find locator: {locator}')

    def switch_to_terminal_window(self):
        id_windows_now = self.driver.current_window_handle
        list_id_window = self.driver.window_handles
        index_id = list_id_window.index(id_windows_now)
        del list_id_window[index_id]
        self.driver.switch_to.window(list_id_window[0])


# KeysightAWG.py
# Gleb Fedorov <vdrhc@gmail.com>
# Alexander Korenkov <soyer94_44@mail.ru>
# Alexey Dmitriev <dmitrmipt@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

from drivers.instrument import Instrument
import visa
import types
import time
import logging
import numpy
import sys
import math
import serial

import matplotlib.pyplot as plt
from ctypes import (Union, Array, c_uint8, c_float, cdll, CDLL)
from enum import Enum
import crcmod
crc8_func = crcmod.mkCrcFun(0x131, initCrc=0x00, xorOut=0x00)

def format_e(n):
    a = '%e' % n
    return a.split('e')[0].rstrip('0').rstrip('.') + 'e' + a.split('e')[1]

class NCS(Instrument):
    '''
    The driver for the Yokogawa_GS210. Default operation regime is CURRENT source.

        CURRENT SOURCE
        Source Range    Range Generated     Resolution      Max. Load Voltage
        1 mA            ±1.20000 mA         10 nA           ±30 V
        10 mA           ±12.0000 mA         100 nA          ±30 V
        100 mA          ±120.000 mA         1 μA            ±30 V
        200 mA          ±200.000 mA         1 μA            ±30 V

        VOLTAGE SOURCE
        Source Range    Range Generated     Resolution      Max. Load Current
        10 mV           ±12.0000 mV         100 nV          --------
        100 mV          ±120.000 mV         1 μV            --------
        1 V             ±1.20000 V          10 μV           ±200 mA
        10 V            ±12.0000 V          100 μV          ±200 mA
        30 V            ±32.000 V           1 mV            ±200 mA
    '''

    current_ranges_supported = [.001, .01, .1, .2]           #possible current ranges supported by current source
    voltage_ranges_supported = [.01, .1, 1, 10, 30]

    def __init__(self, address, volt_compliance = 1, current_compliance = .001):
        '''Create a default NCS object as a current source'''
        Instrument.__init__(self, 'NCS', tags=['physical'])
        self._address = address
        #rm = visa.ResourceManager()
        #self._visainstrument = rm.open_resource(self._address)
        self.ser = serial.Serial(port = 'COM3', baudrate=115200, parity=serial.PARITY_NONE,stopbits=serial.STOPBITS_ONE,bytesize=serial.EIGHTBITS,timeout=None)
        current_range = (-50e-3, 50e-3)
        voltage_range = (-32, 32)

        self._mincurrent = -10e-3
        self._maxcurrent =  10e-3

        self._minvoltage = -1e-6
        self._maxvoltage =  1e-6

        self.add_parameter('current', flags = Instrument.FLAG_GETSET,
        units = 'A', type = float, minval = current_range[0], maxval = current_range[1])

        self.add_parameter('current_compliance', flags = Instrument.FLAG_GETSET,
        units = 'V', type = float, minval = current_range[0], maxval = current_range[1])

        self.add_parameter('voltage', flags = Instrument.FLAG_GETSET,
        units = 'V', type = float, minval = voltage_range[0], maxval = voltage_range[1])

        self.add_parameter('voltage_compliance', flags = Instrument.FLAG_GETSET,
        units = 'V', type = float, minval = voltage_range[0], maxval = voltage_range[1])

        self.add_parameter('status',
        flags = Instrument.FLAG_GETSET, units = '', type = int)

        self.add_parameter('range',
        flags = Instrument.FLAG_GETSET, units = '', type = float)

#        self.add_function("get_id")

        self.add_function("clear")
        self.add_function("set_src_mode_volt")

        temp_cmd_output = [0] * 9
        temp_cmd_output[0] = 0xC0 | 1  # 1 - current source address, as default 1
        temp_cmd_output[1] = 0x70 | 1
        temp_cmd_output[2] = 0x70  # ch1
        temp_cmd_output[3] = 0x70  # ch2
        temp_cmd_output[4] = 0x70  # ch3
        temp_cmd_output[5] = 0x70  # ch4
        temp_cmd_output[6] = 0x70  # ch5
        temp_cmd_output[7] = 0  # reserved
        temp_cmd_output[8] = 0  # reserved
        temp_cmd_output[1 + 1] = 0x70 | 1
        temp_b = bytearray(temp_cmd_output)
        temp_crc = crc8_func(temp_b).to_bytes(1, byteorder='big')
        temp_b += temp_crc
        self.ser.write(temp_b)

        temp_cmd_output = [0] * 9
        temp_cmd_output[0] = 0xC0 | 1  # 1 - current source address, as default 1
        temp_cmd_output[1] = 0xA0 | 1
        temp_cmd_output[2] = 0xA0  # ch1
        temp_cmd_output[3] = 0xA0  # ch2
        temp_cmd_output[4] = 0xA0  # ch3
        temp_cmd_output[5] = 0xA0  # ch4
        temp_cmd_output[6] = 0xA0  # ch5
        temp_cmd_output[7] = 0  # reserved
        temp_cmd_output[8] = 0  # reserved
        temp_cmd_output[1 + 1] = 0xA0 | 1
        temp_z = bytearray(temp_cmd_output)
        temp_crc = crc8_func(temp_z).to_bytes(1, byteorder='big')
        temp_z += temp_crc
        self.ser.write(temp_z)

#        self._visainstrument.write(":SOUR:FUNC CURR")

    #        self.set_voltage_compliance(volt_compliance)
        self.set_current(0)
 #       self.set_status(1)

    class uint8_array(Array):
        _type_ = c_uint8
        _length_ = 4
    class f_type(Union):
        _fields_ = ("float", c_float), ("u8", uint8_array)

    def float_to4bytes(float_data):
        temp_data = f_type()
        temp_data.float = float_data
        return temp_data.u8[:]
    def byteArray_toFloat(data_array, offset = 0):
        temp_data = f_type()
        temp_data.u8[:] = (data_array[0+offset],data_array[1+offset],data_array[2+offset],data_array[3+offset])
        return temp_data.float

    def get_id(self):
        '''Get basic info on device'''
        return "NCS_1"
    #range = 0 - '1uA'... range = 5 - '50mA'
    def ncs_output_enable(self, state=0, channel=1):
        temp_cmd_output = [0] * 9
        temp_cmd_output[0] = 0xC0 | 1  # 1 - current source address, as default 1
        temp_cmd_output[1] = 0x70 | channel
        temp_cmd_output[2] = 0x70  # ch1
        temp_cmd_output[3] = 0x70  # ch2
        temp_cmd_output[4] = 0x70  # ch3
        temp_cmd_output[5] = 0x70  # ch4
        temp_cmd_output[6] = 0x70  # ch5
        temp_cmd_output[7] = 0  # reserved
        temp_cmd_output[8] = 0  # reserved
        temp_cmd_output[channel + 1] = 0x70 | state
        temp_b = bytearray(temp_cmd_output)
        temp_crc = crc8_func(temp_b).to_bytes(1, byteorder='big')
        temp_b += temp_crc
        return temp_b

    def ncs_channel_enable(self, state=0, channel=1):
        temp_cmd_output = [0] * 9
        temp_cmd_output[0] = 0xC0 | 1  # 1 - current source address, as default 1
        temp_cmd_output[1] = 0xA0 | channel
        temp_cmd_output[2] = 0xA0  # ch1
        temp_cmd_output[3] = 0xA0  # ch2
        temp_cmd_output[4] = 0xA0  # ch3
        temp_cmd_output[5] = 0xA0  # ch4
        temp_cmd_output[6] = 0xA0  # ch5
        temp_cmd_output[7] = 0  # reserved
        temp_cmd_output[8] = 0  # reserved
        temp_cmd_output[channel + 2] = 0xA0 | state
        temp_v = bytearray(temp_cmd_output)
        temp_crc = crc8_func(temp_v).to_bytes(1, byteorder='big')
        temp_v += temp_crc
        return temp_v

    def ncs_set_range(self, range = 0, channel = 1):
        temp_cmd_output = [0]*9
        temp_cmd_output[0] = 0xC0 | 1 #1 - current source address, as default 1
        temp_cmd_output[1] = 0x60 | channel
        temp_cmd_output[2] = 0x60 #ch1
        temp_cmd_output[3] = 0x60 #ch2
        temp_cmd_output[4] = 0x60 #ch3
        temp_cmd_output[5] = 0x60 #ch4
        temp_cmd_output[6] = 0x60 #ch5
        temp_cmd_output[7] = 0 #reserved
        temp_cmd_output[8] = 0 #reserved
        temp_cmd_output[channel + 2] = 0x60 | range
        temp_b = bytearray(temp_cmd_output)
        temp_crc = crc8_func(temp_b).to_bytes(1, byteorder = 'big')
        temp_b += temp_crc

        return self.temp_b

    def ncs_set_output_mode(self, mode = 0, channel = 1):
        temp_cmd_output = [0]*9
        temp_cmd_output[0] = 0xC0 | 1 #1 - current source address, as default 1
        temp_cmd_output[1] = 0x30 | channel
        temp_cmd_output[2] = 0x30 #ch1
        temp_cmd_output[3] = 0x30 #ch2
        temp_cmd_output[4] = 0x30 #ch3
        temp_cmd_output[5] = 0x30 #ch4
        temp_cmd_output[6] = 0x30 #ch5
        temp_cmd_output[7] = 0 #reserved
        temp_cmd_output[8] = 0 #reserved
        temp_cmd_output[channel + 1] = 0x30 | mode
        temp_b = bytearray(temp_cmd_output)
        temp_crc = crc8_func(temp_b).to_bytes(1, byteorder = 'big')
        temp_b += temp_crc
        return(temp_b)

    def ncs_set_current(self, current = 0.0, channel = 1):
        temp_cmd_output = [0]*9
        temp_cmd_output[0] = 0xC0 | 1 #1 - current source address, as default 1
        temp_cmd_output[1] = 0xD0 | channel
        temp_current = float_to4bytes(current)
        temp_cmd_output[2] = temp_current[0]
        temp_cmd_output[3] = temp_current[1]
        temp_cmd_output[4] = temp_current[2]
        temp_cmd_output[5] = temp_current[3]
        temp_cmd_output[6] = 0
        temp_cmd_output[7] = 0
        temp_cmd_output[8] = 0
        temp_b = [0]
        temp_b = bytearray(temp_cmd_output)
        temp_crc = crc8_func(temp_b).to_bytes(1, byteorder = 'big')
        temp_b += temp_crc
        return(temp_b)
    def do_set_current(self, current):
        '''Set current'''
        if (self._mincurrent <= current <= self._maxcurrent):

            self.ser.write(ncs_set_current(current, 1))
            time.sleep(0.1)
            sys.stdout.flush()
        else:
            print("Error: current limits,",(self._mincurrent, self._maxcurrent)," exceeded.")

    def do_get_current(self):
        '''Get current'''
        return float(0)

    def do_set_voltage(self, voltage):
        '''Set voltage'''
        if (self._visainstrument.ask(":SOUR:FUNC?") == "CURR\n"):
            print("Tough luck, mode is current source, cannot get voltage.")
            return False
        else:
            if (self._minvoltage < voltage < self._maxvoltage):
                self._visainstrument.write("SOUR:LEVEL %e"%voltage)
                print("Voltage set",format_e(voltage), "V")
            else:
                print("Error: voltage limits exceeded.")

    def do_get_voltage(self):
        '''Get voltage'''
        if (self._visainstrument.ask(":SOUR:FUNC?") == "CURR\n"):
            print("Tough luck, mode is current source, cannot get voltage.")
            return False
        return float(self._visainstrument.ask("SOUR:LEVEL?"))

    def do_set_status(self, status):
        '''
        Turn output on and off

        Parameters:
        -----------
            status: 0 or 1
                0 for off, 1 for on
        '''
        self._visainstrument.write("OUTP "+("ON" if status==1 else "OFF"))

    def do_get_status(self):
        '''Check if output is turned on'''
        return "Off" #self._visainstrument.query("OUTP?")

    def do_get_voltage_compliance(self):
        '''Get compliance voltage'''
        return 5.0 #float(self._visainstrument.ask("SOUR:PROT:VOLT?"))

    def do_set_voltage_compliance(self, compliance):
        '''Set compliance voltage'''
        #if (self._visainstrument.ask(":SOUR:FUNC?") == "VOLT\n"):
        #    print("Tough luck, mode is voltage source, cannot set voltage compliance.")
        #    return False
        #self._visainstrument.write("SOUR:PROT:VOLT %e"%compliance)
        pass

    def do_get_current_compliance(self):
        '''Get compliance voltage'''
        return 5.0e-3 #float(self._visainstrument.ask("SOUR:PROT:CURR?"))

    def do_set_current_compliance(self, compliance):
        '''Set compliance current'''
        #if (self._visainstrument.ask(":SOUR:FUNC?") == "CURR\n"):
        #    print("Tough luck, mode is current source, cannot set current compliance.")
        #    return False
        #self._visainstrument.write("SOUR:PROT:CURR %e"%compliance)
        pass
    def do_get_range(self):
        '''Get current range in A'''
        currange = .001 #self._visainstrument.ask("SOUR:RANG?")[:-1]
        return float(currange)

    def do_set_range(self, maxval):
        '''Set current range in A'''
        #if (self._visainstrument.ask(":SOUR:FUNC?") == "CURR\n"):
        if not (maxval in self.current_ranges_supported):
            print("Given current range is invalid. Please enter valid current range in !!!Amperes!!!\nValid ranges are (in A): {0}".format(self.current_ranges_supported))
            return False
        else:
            if (maxval < 1e-6):
                self.ser.write(ncs_set_range(0, channel = 1))
            if (maxval > 1e-6 and maxval < 10e-6):
                self.ser.write(ncs_set_range(1, channel = 1))
            if (maxval > 10e-6 and maxval < 100e-6):
                self.ser.write(ncs_set_range(2, channel = 1))
            if (maxval > 100e-6 and maxval < 1e-3):
                self.ser.write(ncs_set_range(3, channel = 1))
            if (maxval > 1e-3 and maxval < 10e-3):
                self.ser.write(ncs_set_range(4, channel = 1))
            if (maxval > 10e-3 and maxval < 50e-3):
                self.ser.write(ncs_set_range(5, channel = 1))
        #if(self._visainstrument.ask(":SOUR:FUNC?") == "VOLT\n"):
            #if not (maxval in self.voltage_ranges_supported):
               # print("Given voltage range is invalid. Please enter valid voltage range in !!!Volts!!!\nValid ranges are (in A): {0}".format(self.voltage_ranges_supported))
               # return False
            #else:
               # self._visainstrument.write("SOUR:RANG %e"%maxval)

    def set_appropriate_range(self, maxcurrent=1E-3, mincurrent=-1E-3):
        '''Detect which range includes limits and set it'''

        required_current = max(abs(maxcurrent), abs(mincurrent))
        for current_range in self.current_ranges_supported:
            if current_range >= required_current:
                self.set_range(current_range)
                return True
            if(current_range == self.current_ranges_supported[-1]):
                print("Current is too big, can't handle it!")
                return False

    def set_src_mode_volt(self, current_compliance = .001):
        '''
        Changes mode from current to voltage source, compliance current is given as an argument

        Returns:
            True if the mode was changed, False otherwise
        '''
        #if (self._visainstrument.ask(":SOUR:FUNC?") == "VOLT\n"):
        #   return False
        #else:
            #self._visainstrument.write(":SOUR:FUNC VOLT")
            #self.set_current_compliance(current_compliance)
        return True

    def set_src_mode_curr(self, voltage_compliance = 1):
        '''
        Changes mode from voltage to current source, compliance voltage is given as an argument

        Returns:
            True if the mode was changed, False otherwise
        '''
        #if (self._visainstrument.ask(":SOUR:FUNC?") == "CURR\n"):
        #    return False
        #else:
        #    self._visainstrument.write(":SOUR:FUNC CURR")
        #    self.set_voltage_compliance(voltage_compliance)
        return True

    def set_current_limits(self, mincurrent = -1E-3, maxcurrent = 1E-3):
        ''' Sets a limits within the range if needed for safe sweeping'''
        if (True == True):#(self._visainstrument.ask(":SOUR:FUNC?") == "CURR\n"):
            if mincurrent >= -1.2*self.get_range():
                   self._mincurrent = mincurrent
            else:
                print("Too low mincurrent asked to set.")
            if maxcurrent <= 1.2*self.get_range():
                self._maxcurrent = maxcurrent
            else:
                print("Too high maxcurrent asked to set.")
        #else:
        #    print("Go in current mode first.")

    def set_voltage_limits(self, minvoltage = -1E-3, maxvoltage = 1E-3):
        ''' Sets a voltage limits within the range if needed for safe sweeping'''
        if (True == True): #(self._visainstrument.ask(":SOUR:FUNC?") == "VOLT\n"):
            if minvoltage >= -1*self.get_range():
                   self._minvoltage = minvoltage
            else:
                print("Too low minvoltage asked to set.")
            if maxvoltage <= self.get_range():
                self._maxvoltage = maxvoltage
            else:
                print("Too high maxvoltage asked to set.")
        else:
            print("Go in voltage mode first.")

    def clear(self):
        """
        Clear the event register, extended event register, and error queue.
        """
        #self._visainstrument.write("*CLS")
        pass

    # TODO:
    #       Дописать переключение режимов CURRENT/VOLTAGE
    #       Дописать недостающие команды и параметры в кострукторе для напряжения ( add_parameter('voltage', ...) и операции do_set_voltage и иже с ним)


from typing import Dict, List
import pathlib

from torch.utils import data
from torch import nn
from torchvision import io
from torchvision.io.image import ImageReadMode

class ChestXrayDataset(data.Dataset):
    def __init__(self, data_dir: str,
                 transform: nn.Sequential,
                 data_type: str,
                 class_mapping: Dict[str, int],
                 image_ext: List[str] = [".jpeg"]):
        assert data_type in ("train", "test", "val")
        self.data_dir = data_dir
        self.transform = transform
        self.data_type = data_type
        self.class_mapping = class_mapping
        self._image_ext = set(image_ext)
        self._images_path = []
        self._class_labels = []
        self._fill_image_list()

    def _fill_image_list(self):
        image_dir = pathlib.Path(self.data_dir, self.data_type)
        assert image_dir.is_dir(), f"{image_dir} is not a directory"

        for entry in image_dir.rglob("*"):
            if entry.is_file() and entry.suffix.lower() in self._image_ext and entry.parent.name in self.class_mapping:
                self._images_path.append(entry)
                self._class_labels.append(self.class_mapping[entry.parent.name])

    def __len__(self) -> int:
        return len(self._images_path)

    def __getitem__(self, index) -> dict:
        image = io.read_image(str(self._images_path[index]), mode=ImageReadMode.GRAY)
        image_path = self._images_path[index].relative_to(self.data_dir).as_posix()
        return {"image": self.transform(image), "label": self._class_labels[index], "image_path": image_path}


import tkinter as tk
from tkinter import ttk
from tkinter import *
from tkhtmlview import HTMLLabel
import markdown
from PIL import Image, ImageTk
import fitz
import logging
import os

from agents.image_agent import ImageAgent
from agents.csv_agent import CSVHandler

from gui.chat_tab import ChatTab

from constants import *
DIRECTORIES = [
    KB_DIR,
    HISTORY_DIR,
    CSV_DIR,
    IMG_DIR,
    CODE_DIR,
    LOG_DIR,
    DATA_DIR,
    PROJECTS_DIR
    ]

class FileTab:
    def __init__(self, parent, file_tab):
        self.root = parent
        self.parent = parent
        self.image_agent = ImageAgent()
        self.csv_agent = CSVHandler()
        self.file_tab = file_tab
        self.chat_tab = ChatTab(parent=self.root, chat_window=self.root.chat_window)
        self.create_widgets_file_manager()

    def create_widgets_file_manager(self):
        self.file_tab.grid_rowconfigure(0, weight=1)
        self.file_tab.grid_columnconfigure(0, weight=0)
        self.file_tab.grid_columnconfigure(1, weight=0)
        self.file_tab.grid_columnconfigure(2, weight=4)

        ttk.Label(self.file_tab, text ="Files").grid(column = 0, row = 0, padx = 30, pady = 30, sticky='nsew')
        self.create_file_tab_sidebar(self.file_tab)
        self.create_file_tab_main(self.file_tab)
        self.refresh_folders_button = ttk.Button(self.file_tab, text="Refresh", command=self.file_tab_refresh_files)
        self.refresh_folders_button.grid(row=1, column=0, sticky=E)
        self.add_to_chat_button = ttk.Button(self.file_tab, text="Add to Chat History", command=self.file_tab_add_to_chat_history)
        self.add_to_chat_button.grid(row=2, column=0, sticky=E)

    def create_file_tab_sidebar(self, parent):
        self.tree = ttk.Treeview(parent)
        self.tree.grid(row=0, column=0, sticky=N+S+E+W)

        scrollbar = ttk.Scrollbar(parent, orient="vertical", command=self.tree.yview)
        scrollbar.grid(row=0, column=1, sticky=N+S)
        self.tree.config(yscrollcommand=scrollbar.set)

        # Add directories to tree
        for directory in DIRECTORIES:
            root = self.tree.insert('', 'end', text=directory, open=True)
            self.process_directory(root, directory)

        # Add event handler for selecting an item
        self.tree.bind('<<TreeviewSelect>>', self.file_tab_on_file_select)

        # Add event handler for opening a directory
        self.tree.bind('<<TreeviewOpen>>', self.file_tab_on_directory_open)

    def process_directory(self, parent, path):
        # Get all sub directories and files
        for p in os.scandir(path):
            # If file, add file
            if p.is_file():
                # Add only supported files
                if p.name.endswith(('.ts', '.txt', 'pdf', '.csv', '.py', '.js', '.rs', '.c', '.cpp', '.java', '.cs', '.rb', '.php', '.swift', '.go', '.lua', '.groovy', '.kotlin', '.dart', '.f', '.f90', '.f95', '.f03', '.f08', '.for', '.h', '.hh', '.hpp', '.hxx', '.pl', '.asm', '.sh', '.bat', '.html', '.css', '.scss', '.sass', '.less', '.json', '.xml', '.yaml', '.yml', '.sql', '.md', '.markdown', '.r', '.Rmd', '.m', '.mm', '.p', '.pas', '.pli', '.pl1', '.cob', '.cbl', '.j', '.jl', '.erl', '.hs', '.elm', '.scala', '.sc', '.clj', '.cljs', '.edn', '.coffee', '.kt', '.hx', '.pde', '.ino', '.cls', '.bas', '.frm', '.vba', '.vbs', '.d', '.ada', '.adb', '.ads', '.ml', '.mli', '.fs', '.fsi', '.fsx', '.v', '.vh', '.vhd', '.vhdl', '.tex', '.sty', '.cls', '.clojure', '.png', '.jpg', '.jpeg')):
                    self.tree.insert(parent, 'end', text=p.name)

    def create_file_tab_main(self, parent):
        self.viewer_frame = ttk.Frame(parent)
        self.viewer_frame.grid(row=0, column=2, sticky=N+S+E+W)

        self.md_viewer = HTMLLabel(self.viewer_frame, html="
Select a file from the list
")
        self.md_viewer.pack(fill='both', expand=True)

        # Create a label for images
        self.image_label = ttk.Label(self.viewer_frame)
        self.image_label.pack(fill='both', expand=True)

    def file_tab_on_file_select(self, evt):
        # Get selected file name
        selected_item_id = self.tree.selection()[0]
        file_name = self.tree.item(selected_item_id, 'text')

        file_path = self.file_tab_get_full_path(selected_item_id)

        # Check if it's a file and not a directory
        if os.path.isfile(file_path):
            # Read the file
            if file_name.endswith('.md'):
                with open(file_path, 'r') as f:
                    file_content = f.read()
                html = markdown.markdown(file_content)
                self.md_viewer.set_html(html)
                self.md_viewer.pack(fill='both', expand=True)  # Make sure it's visible
                self.image_label.pack_forget()  # Hide the image label
            elif file_name.endswith(('.ts', 'txt', 'csv', '.py', '.js', '.rs', '.c', '.cpp', '.java', '.cs', '.rb', '.php', '.swift', '.go', '.lua', '.groovy', '.kotlin', '.dart', '.f', '.f90', '.f95', '.f03', '.f08', '.for', '.h', '.hh', '.hpp', '.hxx', '.pl', '.asm', '.sh', '.bat', '.html', '.css', '.scss', '.sass', '.less', '.json', '.xml', '.yaml', '.yml', '.sql', '.md', '.markdown', '.r', '.Rmd', '.m', '.mm', '.p', '.pas', '.pli', '.pl1', '.cob', '.cbl', '.j', '.jl', '.erl', '.hs', '.elm', '.scala', '.sc', '.clj', '.cljs', '.edn', '.coffee', '.kt', '.hx', '.pde', '.ino', '.cls', '.bas', '.frm', '.vba', '.vbs', '.d', '.ada', '.adb', '.ads', '.ml', '.mli', '.fs', '.fsi', '.fsx', '.v', '.vh', '.vhd', '.vhdl', '.tex', '.sty', '.cls', '.clojure')):
                with open(file_path, 'r') as f:
                    file_content = f.read()
                html = '
{}
'.format(file_content)
                self.md_viewer.set_html(html)
                self.md_viewer.pack(fill='both', expand=True)  # Make sure it's visible
                self.image_label.pack_forget()  # Hide the image label
            elif file_name.lower().endswith(('.png', '.jpg', '.jpeg')):
                image = Image.open(file_path)
                photo = ImageTk.PhotoImage(image)
                self.image_label.config(image=photo)
                self.image_label.image = photo  # Keep a reference to prevent garbage collection
                self.image_label.pack(fill='both', expand=True)  # Make sure it's visible
                self.md_viewer.pack_forget()  # Hide the HTMLLabel
            elif file_name.lower().endswith('.pdf'):  # Add support for PDF
                doc = fitz.open(file_path)
                page = doc.load_page(0)  # number of page
                pix = page.get_pixmap(matrix=fitz.Matrix(2, 2))
                raw = pix.samples  # get the raw pixel data
                image = Image.frombytes("RGB", [pix.width, pix.height], raw)
                photo = ImageTk.PhotoImage(image)
                self.image_label.config(image=photo)
                self.image_label.image = photo  # Keep a reference to prevent garbage collection
                self.image_label.pack(fill='both', expand=True)  # Make sure it's visible
                self.md_viewer.pack_forget()  # Hide the HTMLLabel
            else:
                html = '
Unsupported file type
'
                self.md_viewer.set_html(html)
                self.md_viewer.pack(fill='both', expand=True)  # Make sure it's visible
                self.image_label.pack_forget()  # Hide the image label

    def file_tab_get_full_path(self, item_id):
        parent_id = self.tree.parent(item_id)
        if parent_id:  # if item has a parent
            return os.path.join(self.file_tab_get_full_path(parent_id), self.tree.item(item_id, 'text'))
        else:  # if item has no parent
            return self.tree.item(item_id, 'text')

    def file_tab_on_directory_open(self, event):
        # Get the directory that's being opened
        directory_id = self.tree.focus()

        # Delete the old contents of the directory
        for child_id in self.tree.get_children(directory_id):
            self.tree.delete(child_id)

        # Populate the directory with its subdirectories and files
        directory_path = self.file_tab_get_full_path(directory_id)
        self.process_directory(directory_id, directory_path)

    def file_tab_refresh_files(self):
        for i in self.tree.get_children():
            self.tree.delete(i)

        # List of directories
        directories = DIRECTORIES

        # Refresh directories
        for directory in directories:
            root = self.tree.insert('', 'end', text=directory, open=True)
            self.process_directory(root, directory)

    def file_tab_add_to_chat_history(self):
        # Get the currently selected file
        selected_item_id = self.tree.selection()[0]
        #selected_file = self.tree.item(selected_item_id, 'text')

        file_path = self.file_tab_get_full_path(selected_item_id)

        # Check if it's a file and not a directory
        if os.path.isfile(file_path):
            # If csv file, get columns
            if file_path.endswith('.csv'):
                columns = self.csv_agent.read_csv_columns(file_path)
                self.chat_tab.conversation.append({
                    "role": "user",
                    "content": f'Columns: {columns}\n',
                })

                # Convert the column list to HTML format
                columns_html = columns.replace('- ', '
').replace('\n', '
')
                columns_html = f'
{columns_html}

'

                content_html = f'
You:
{columns_html}

'
                self.chat_tab.current_html += content_html
                self.chat_tab.text_area.set_html(self.current_html)
                return
            # If image file, say no
            elif file_path.endswith(('.png', '.jpg', '.jpeg', '.pdf')):
                logging.info('Image not supported')
                return

            # Open and read the file
            with open(file_path, 'r') as file:
                content = file.read()

            # Append to chat history
            self.chat_tab.conversation.append({
                "role": "user",
                "content": f'{content}\n',
            })

            # Create HTML for the loaded content and append to the current HTML
            # Here, newlines are replaced with
 for proper HTML formatting
            content_html = f'
You: Content loaded from {file_path}:
'
            with open(file_path, 'r') as file:
                for i, line in enumerate(file, start=1):
                    # Append to chat history with line numbers
                    # Add line numbers to the HTML content
                    content_html += f'{i}: {line}
'
            self.chat_tab.current_html += content_html
            self.chat_tab.text_area.set_html(self.chat_tab.current_html)

            # You may want to automatically scroll to the end of the text area
            self.chat_tab.text_area.see('end')