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

"""package datalogger-gui
author    Baptiste Marechal, refactoring and update to Python3 by Benoit Dubois
copyright FEMTO-ST, 2022
license   GPL v3.0+
brief     A GUI interfaced software to log multiple instruments via ethernet,
          usb or serial connection.
"""

import os, sys
import inspect, threading
import time
from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QComboBox, \
	QLineEdit, QDoubleSpinBox, QPushButton, QCheckBox, QLabel, QGridLayout, \
	QListWidget
from PyQt6.QtCore import pyqtSlot
import datalogger.instruments as instruments

DEFAULT_DATA_DIR = os.path.abspath(os.path.expanduser('~/server/data/'))
DEFAULT_FILE_DURATION = 24*3600  # in second


class AcqRoutine():
    """Class handling acquisition process.
    """

    def __init__(self, instrument, channels, vtypes, address, samplingtime,
                 path=DEFAULT_DATA_DIR, fileduration=DEFAULT_FILE_DURATION):
        instrument_module = getattr(instruments, instrument)
        self.instrument = getattr(instrument_module, instrument)(channels,
                                                                 vtypes,
                                                                 address)
        self.path = path
        self.sampling_time = samplingtime
        self.fileduration = fileduration
        self.t0 = None
        self.filename = ''
        self.data_file = None
        self.thread = None

    def __del__(self):
        self.stop()

    def make_tree(self):
        """'Move application' to data directory. If directory path does not
        exist, create it.
        :returns: True if move to data directory succeed, else False (bool)
        """
        base_data_dir = os.path.abspath(self.path)
        print(base_data_dir)
        if not os.path.isdir(base_data_dir):
            print("Error: data directory does not exist.")
            return False
        year = time.strftime("%Y", time.gmtime(self.t0))
        month = time.strftime("%Y-%m", time.gmtime(self.t0))
        year_data_dir = os.path.join(base_data_dir, year)
        if not os.path.isdir(year_data_dir):
            os.mkdir(year_data_dir)
        month_data_dir = os.path.join(year_data_dir, month)
        if not os.path.isdir(month_data_dir):
            os.mkdir(month_data_dir)
        os.chdir(month_data_dir)
        print(month_data_dir)
        return True

    def connect(self):
        self.t0 = time.time()
        self.filename = time.strftime("%Y%m%d-%H%M%S", time.gmtime(self.t0)) \
            + '-' + self.instrument.model() + '.dat'
        if not self.make_tree():
            print("Connection failed")
            return
        self.data_file = open(self.filename, 'w', 1)
        self.instrument.connect()

    def start(self):
        tic = time.time()

        if (time.time() - self.t0 >= self.fileduration) & (self.fileduration > 0 ):
            self.data_file.close()
            self.t0 = time.time()
            self.filename = time.strftime("%Y%m%d-%H%M%S", time.gmtime(self.t0)) + '-' + self.instrument.model() + '.dat'
            self.make_tree()
            self.data_file = open(self.filename, 'w', 1)

        # epoch time
        epoch = time.time()
        # MJD time
        mjd = epoch / 86400.0 + 40587
        # Format meas values
        meas = self.instrument.get_value()
        meas = meas.replace(",", "\t")
        meas = meas.replace(";", "\t")
        meas = meas.replace("+", "")
        meas = meas.replace("E", "e")

        string = "%f\t%f\t%s" % (epoch, mjd, meas)
        string = string.replace("\t\t", "\t")
        self.data_file.write(string)
        print(string[:-1])  # remove final '\n'

        self.thread = threading.Timer(self.sampling_time - (time.time() - tic), self.start)
        self.thread.start()

    def stop(self):
        if self.thread:
            self.thread.cancel()
        #self.instrument.disconnect()  # Need to check connection before...
        if self.data_file:
            try:
                self.data_file.close()
            except IOError:
                pass


class DataloggerGui(QMainWindow):
    """Class defining datalogger (G)UI.
    """

    def __init__(self):
        super().__init__()
        self.set_window()

    def set_window(self):
        self.resize(640, 480)
        self.setWindowTitle('datalogger-gui')

        self.wid = QWidget()
        self.setCentralWidget(self.wid)
        self.layout = QGridLayout()
        self.wid.setLayout(self.layout)

        self.combo_inst = QComboBox()
        self.layout.addWidget(self.combo_inst, 0, 0)

        self.address = QLineEdit()
        self.address.setMinimumWidth(140)
        self.address.setMaximumWidth(140)
        self.layout.addWidget(self.address, 0, 1)

        self.sampling_time = QDoubleSpinBox()
        #self.sampling_time.setMinimumWidth(60)
        #self.sampling_time.setMaximumWidth(60)
        self.sampling_time.setMinimum(0.1)
        self.sampling_time.setMaximum(1000)
        self.sampling_time.setSingleStep(0.1)
        self.sampling_time.setValue(1)
        self.layout.addWidget(self.sampling_time, 0, 2)

        self.start_button = QPushButton()
        self.start_button.setText('Start log')
        self.layout.addWidget(self.start_button, 99, 0)
        self.start_button.setEnabled(False)

        self.stop_button = QPushButton()
        self.stop_button.setText('Stop log')
        self.layout.addWidget(self.stop_button, 99, 1)
        self.stop_button.setEnabled(False)

        self.text_display = QLabel()
        self.text_display.setText('>>')
        self.layout.addWidget(self.text_display, 99, 2)

    def idle_state(self):
        """Set UI in 'idle' state: handles widget state (enable/disable) to be
        used when application is idle.
        """
        self.start_button.setEnabled(True)
        self.stop_button.setEnabled(False)
        self.address.setEnabled(True)
        self.sampling_time.setReadOnly(False)
        self.combo_inst.setEnabled(True)

    def log_state(self):
        """Set UI in 'log' state: handles widget state (enable/disable)to be
        used when application is logging data.
        """
        self.start_button.setEnabled(False)
        self.stop_button.setEnabled(True)
        self.address.setEnabled(False)
        self.sampling_time.setReadOnly(True)
        self.combo_inst.setEnabled(False)


class DataLogger():
    """Class defining the application and its behavior.
    """

    def __init__(self):
        self.ui = DataloggerGui()
        self.sampling_time = None
        self.inst_to_log = ''
        self.address_to_log = ''
        self.ch_to_log = []
        self.vtype_to_log = []
        self.ts = None
        self.my_log = None
        self.set_combo_inst()
        self.update_signal()
        self.set_signals_slots()
        self.idle_state()

    def init_ui(self):
        self.set_combo_inst()

    def set_combo_inst(self):
        for name, obj in inspect.getmembers(instruments):
            if inspect.ismodule(obj) and not name.startswith('__')  \
               and not name.startswith('abstract'):
                self.ui.combo_inst.addItem(name)

    def start_log(self):
        self.log_state()
        self.my_log.connect()
        self.my_log.start()

    def stop_log(self):
        self.idle_state()
        self.my_log.stop()

    def set_signals_slots(self):
        self.ui.combo_inst.currentIndexChanged.connect(self.update_signal)
        self.ui.start_button.clicked.connect(self.start_log)
        self.ui.stop_button.clicked.connect(self.stop_log)

    def close_event(self):
        try:
            self.stop_log()
        except:
            pass
        print('Done')

    def update_signal(self):
        for i in reversed(range(5, self.ui.layout.count())):
            self.ui.layout.itemAt(i).widget().setParent(None)
        channels_availables = getattr(
            instruments,
            self.ui.combo_inst.currentText()).ALL_CHANNELS
        vtypes_availables = getattr(
            instruments,
            self.ui.combo_inst.currentText()).ALL_VAL_TYPE
        default_address = getattr(
            instruments,
            self.ui.combo_inst.currentText()).ADDRESS
        self.ui.address.setText(default_address)

        self.check_box_channels = [None]*len(channels_availables)
        self.ch_list_vtypes = [None]*len(self.check_box_channels)

        for i in range(len(self.check_box_channels)):
            self.check_box_channels[i] = QCheckBox()
            self.check_box_channels[i].setText(channels_availables[i])
            self.check_box_channels[i].setChecked(False)
            self.ch_list_vtypes[i] = QListWidget()
            for vtype in vtypes_availables:
                self.ch_list_vtypes[i].addItem(vtype)
            self.ch_list_vtypes[i].setCurrentRow(0)
            self.ui.layout.addWidget(self.check_box_channels[i], i+3, 1)
            self.ui.layout.addWidget(self.ch_list_vtypes[i], i+3, 2)
            self.check_box_channels[i].stateChanged.connect(self.info_signal)
            self.ch_list_vtypes[i].currentItemChanged.connect(self.info_signal)

        self.ui.address.textChanged.connect(self.info_signal)
        self.ui.sampling_time.valueChanged.connect(self.info_signal)

        self.info_signal()

    def info_signal(self):
        self.inst_to_log = self.ui.combo_inst.currentText()
        self.address_to_log = self.ui.address.text()
        self.ch_to_log = []
        self.vtype_to_log = []
        self.ts = self.ui.sampling_time.value()

        for i in range(len(self.check_box_channels)):
            if self.check_box_channels[i].isChecked():
                self.ch_list_vtypes[i].setEnabled(True)
                self.ch_to_log.append(str(self.check_box_channels[i].text()))
                self.vtype_to_log.append(
                    str(self.ch_list_vtypes[i].currentItem().text()))
            else:
                self.ch_list_vtypes[i].setEnabled(False)

        all_channels_unchecked = False
        for i in self.check_box_channels:
            all_channels_unchecked = all_channels_unchecked or i.isChecked()
        if all_channels_unchecked is False:
            self.ui.start_button.setEnabled(False)
        else:
            self.ui.start_button.setEnabled(True)

        self.ui.text_display.setText(
            '>> %s@%s - %s - %s - %d'%(self.inst_to_log,
                                       self.address_to_log,
                                       self.ch_to_log,
                                       self.vtype_to_log,
                                       self.ts))

        self.my_log = AcqRoutine(self.inst_to_log, self.ch_to_log,
                                 self.vtype_to_log, self.address_to_log,
                                 self.ts)

    def idle_state(self):
        """Set UI in 'idle' state: handles widget state (enable/disable) to be
        used when application is idle.
        """
        self.ui.idle_state()
        for i in range(len(self.check_box_channels)):
            if self.check_box_channels[i].isChecked():
                self.check_box_channels[i].setEnabled(True)
                self.ch_list_vtypes[i].setEnabled(True)
            else:
                self.check_box_channels[i].setEnabled(True)
                self.ch_list_vtypes[i].setEnabled(False)

    def log_state(self):
        """Set UI in 'log' state: handles widget state (enable/disable)to be
        used when application is logging data.
        """
        self.ui.log_state()
        for i in self.check_box_channels:
            i.setEnabled(False)
        for i in self.ch_list_vtypes:
            i.setEnabled(False)


# =============================================================================
def test_ui():
    """Start GUI to check is look.
    """
    app = QApplication(sys.argv)
    ui = DataloggerGui()
    ui.show()
    ui.setVisible(True)
    sys.exit(app.exec())


# =============================================================================
def main():
    """Main function.
    """
    app = QApplication(sys.argv)
    datalogger = DataLogger()
    datalogger.ui.show()
    datalogger.ui.setVisible(True)
    sys.exit(app.exec())


# =============================================================================
#test_ui()
main()

#==============================================================================

# class acq_routine():
# 	def __init__(self, instrument, channels, vtypes, address, samplingtime, path = os.getcwd(), fileduration = 24*3600):
# 		exec('self.instrument = instruments.%s.%s(%s, %s, "%s")'%(instrument, instrument, channels, vtypes, address))
# 		self.path = path
# 		self.samplingtime = samplingtime
# 		self.fileduration = fileduration

# 	def makeTree(self):
# 		try:
# 			year = time.strftime("%Y", time.gmtime(self.t0))
# 			month = time.strftime("%Y-%m", time.gmtime(self.t0))
# 			os.chdir(self.path + '/' + year + '/' + month)
# 		except:
# 			try:
# 				os.chdir(self.path + '/' + year)
# 				os.mkdir(month)
# 				os.chdir(self.path + '/' + year + '/' + month)
# 			except:
# 				os.chdir(self.path)
# 				os.mkdir(year)
# 				os.chdir(self.path + '/' + year)
# 				os.mkdir(month)
# 				os.chdir(self.path + '/' + year + '/' + month)

# 	def connect(self):
# 		self.instrument.connect()

# 		self.t0 = time.time()
# 		self.filename = time.strftime("%Y%m%d-%H%M%S", time.gmtime(self.t0)) + '-' + self.instrument.model() + '.dat'
# 		self.makeTree()
# 		self.data_file = open(self.filename, 'wr', 0)

# 	def start(self):
# 		tic = time.time()

# 		if (time.time() - self.t0 >= self.fileduration) & (self.fileduration >0 ):
# 			self.data_file.close()

# 			self.t0 = time.time()
# 			self.filename = time.strftime("%Y%m%d-%H%M%S", time.gmtime(self.t0)) + '-' + self.instrument.model() + '.dat'
# 			self.makeTree()
# 			self.data_file = open(self.filename, 'wr', 0)

# 		#epoch time
# 		epoch = time.time()
# 		#MJD time
# 		mjd = epoch / 86400.0 + 40587
# 		# Meas values
# 		meas = self.instrument.getValue()
# 		meas = meas.replace(",", "\t")
# 		meas = meas.replace(";", "\t")
# 		meas = meas.replace("+", "")
# 		meas = meas.replace("E", "e")

# 		string = "%f\t%f\t%s" % (epoch, mjd, meas)
# 		string = string.replace("\t\t", "\t")
# 		self.data_file.write(string) # Write in a file
# 		print(string)

# 		self.thread = threading.Timer(self.samplingtime - (time.time() - tic), self.start)
# 		self.thread.start()

# 	def stop(self):
# 		self.thread.cancel()
# 		self.instrument.disconnect()
# 		self.data_file.close()


# #==============================================================================

# class mainGui():
# 	def __init__(self):
# 		self.setWindow()
# 		self.setSignalsSlots()
# 		self.runApp()

# 	def setWindow(self):
# 		self.a = QApplication(sys.argv)
# 		self.w = QMainWindow()
# 		self.w.resize(640, 480)
# 		self.w.setWindowTitle('datalogger-gui')

# 		self.wid = QWidget()
# 		self.w.setCentralWidget(self.wid)
# 		self.layout = QGridLayout()
# 		self.wid.setLayout(self.layout)

# 		self.comboInst = QComboBox()
# 		self.layout.addWidget(self.comboInst, 0, 0)

# 		self.address = QLineEdit()
# 		self.address.setMinimumWidth(140)
# 		self.address.setMaximumWidth(140)
# 		self.layout.addWidget(self.address, 0, 1)

# 		self.samplingtime = QDoubleSpinBox()
# 		#self.samplingtime.setMinimumWidth(60)
# 		#self.samplingtime.setMaximumWidth(60)
# 		self.samplingtime.setMinimum(0.1)
# 		self.samplingtime.setMaximum(1000)
# 		self.samplingtime.setSingleStep(0.1)
# 		self.samplingtime.setValue(1)
# 		self.layout.addWidget(self.samplingtime, 0, 2)

# 		self.startButton = QPushButton()
# 		self.startButton.setText('Start log')
# 		self.layout.addWidget(self.startButton, 99, 0)
# 		self.startButton.setEnabled(False)

# 		self.stopButton = QPushButton()
# 		self.stopButton.setText('Stop log')
# 		self.layout.addWidget(self.stopButton, 99, 1)
# 		self.stopButton.setEnabled(False)

# 		self.textDisplay = QLabel()
# 		self.textDisplay.setText('>>')
# 		self.layout.addWidget(self.textDisplay, 99, 2)

# 		self.setComboInst()
# 		self.updateSignal()

# 	def setComboInst(self):
# 		for name, obj in inspect.getmembers(instruments):
# 			if inspect.ismodule(obj) and name.startswith('__') == False and name.startswith('abstract') == False:
# 				print(name, obj)
# 				self.comboInst.addItem(name)

# 	def setSignalsSlots(self):
# 		self.comboInst.currentIndexChanged.connect(lambda: self.updateSignal())
# 		self.startButton.clicked.connect(lambda: self.startLog())
# 		self.stopButton.clicked.connect(lambda: self.stopLog())

# 	def runApp(self):
# 		self.w.show()
# 		self.a.aboutToQuit.connect(self.closeEvent)
# 		sys.exit(self.a.exec())

# 	def closeEvent(self):
# 		try:
# 			self.stopLog()
# 		except:
# 			pass
# 		print('Done')

# 	@pyqtSlot()
# 	def updateSignal(self):
# 		for i in reversed(range(5, self.layout.count())):
# 			self.layout.itemAt(i).widget().setParent(None)

# 		defaultAddress = ''
# 		channelsAviables = []
# 		vtypesAviables = []

# 		exec('channelsAviables = instruments.%s.ALL_CHANNELS'%self.comboInst.currentText())
# 		exec('vtypesAviables = instruments.%s.ALL_VAL_TYPE'%self.comboInst.currentText())
# 		exec('defaultAddress = instruments.%s.ADDRESS'%self.comboInst.currentText())

# 		self.address.setText(defaultAddress)

# 		self.checkBoxChannels = [None]*len(channelsAviables)
# 		self.chListVtypes = [None]*len(self.checkBoxChannels)

# 		for i in range(len(self.checkBoxChannels)):
# 			self.checkBoxChannels[i] = QCheckBox()
# 			self.checkBoxChannels[i].setText(channelsAviables[i])
# 			self.checkBoxChannels[i].setChecked(False)
# 			self.chListVtypes[i] = QListWidget()
# 			for vtype in vtypesAviables:
# 				self.chListVtypes[i].addItem(vtype)
# 			self.chListVtypes[i].setCurrentRow(0)
# 			self.layout.addWidget(self.checkBoxChannels[i], i+3, 1)
# 			self.layout.addWidget(self.chListVtypes[i], i+3, 2)
# 			self.checkBoxChannels[i].stateChanged.connect(self.infoSignal)
# 			self.chListVtypes[i].currentItemChanged.connect(self.infoSignal)

# 		self.address.textChanged.connect(lambda: self.infoSignal())
# 		self.samplingtime.valueChanged.connect(lambda: self.infoSignal())

# 		self.infoSignal()

# 	@pyqtSlot()
# 	def infoSignal(self):
# 		self.instToLog = self.comboInst.currentText()
# 		self.addressToLog = self.address.text()
# 		self.chToLog = []
# 		self.vTypeToLog = []
# 		self.ts = self.samplingtime.value()

# 		for i in range(len(self.checkBoxChannels)):
# 			if self.checkBoxChannels[i].isChecked():
# 				self.chListVtypes[i].setEnabled(True)
# 				self.chToLog.append(str(self.checkBoxChannels[i].text()))
# 				self.vTypeToLog.append(str(self.chListVtypes[i].currentItem().text()))
# 			else:
# 				self.chListVtypes[i].setEnabled(False)

# 		allChannelsUnchecked = False
# 		for i in self.checkBoxChannels:
# 			allChannelsUnchecked = allChannelsUnchecked or i.isChecked()
# 		if allChannelsUnchecked == False:
# 			self.startButton.setEnabled(False)
# 		else:
# 			self.startButton.setEnabled(True)

# 		self.textDisplay.setText('>> %s@%s - %s - %s - %d'%(self.instToLog, self.addressToLog, self.chToLog, self.vTypeToLog, self.ts))

# 		self.myLog = acq_routine(self.instToLog, self.chToLog, self.vTypeToLog, self.addressToLog, self.ts)

# 	@pyqtSlot()
# 	def startLog(self):
# 		self.startButton.setEnabled(False)
# 		self.stopButton.setEnabled(True)
# 		self.address.setEnabled(False)
# 		self.samplingtime.setReadOnly(True)
# 		self.comboInst.setEnabled(False)
# 		for i in self.checkBoxChannels:
# 			i.setEnabled(False)
# 		for i in self.chListVtypes:
# 			i.setEnabled(False)
# 		self.myLog.connect()
# 		self.myLog.start()

# 	@pyqtSlot()
# 	def stopLog(self):
# 		self.startButton.setEnabled(True)
# 		self.stopButton.setEnabled(False)
# 		self.address.setEnabled(True)
# 		self.samplingtime.setReadOnly(False)
# 		self.comboInst.setEnabled(True)
# 		for i in range(len(self.checkBoxChannels)):
# 			if self.checkBoxChannels[i].isChecked():
# 				self.checkBoxChannels[i].setEnabled(True)
# 				self.chListVtypes[i].setEnabled(True)
# 			else:
# 				self.checkBoxChannels[i].setEnabled(True)
# 				self.chListVtypes[i].setEnabled(False)
# 		self.myLog.stop()


# #==============================================================================
# if __name__ == "__main__":
# 	mainGui()
