#!/usr/bin/python3
''' 
  A GUI to control runCosmo.py 

    - select and edit configuration files 
    - select working direcotory
    - start data taking via runCosmo.py
'''

from builtins import super
import sys
import os
import time
import yaml
import subprocess

from PyQt5.QtWidgets import QMessageBox
from .CosmoGuiUi import *  # import code generated by designer-qt5

CONFIG_ENVIRONMENT_file = 'picocosmo.cfg'


class ComoGuiUiInterface(Ui_CosmoWindow):
    '''interface to class generated by designer-qt5'''

    def MB_Question(self, Title, Text):
        # wrapper for QMessageBox Question yes/abort
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Question)
        msg.setWindowTitle(Title)
        msg.setText(Text)
        msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        return msg.exec_()

    def MB_YesNo(self, Title, Text):
        # wrapper for QMessageBox Question yes/abort
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Question)
        msg.setWindowTitle(Title)
        msg.setText(Text)
        msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        return msg.exec_()

    def MB_Info(self, Title, Text):
        # wrapper for QMessageBox Info
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Information)
        msg.setWindowTitle(Title)
        msg.setText(Text)
        msg.setStandardButtons(QMessageBox.Ok)
        return msg.exec_()

    def MB_Warning(self, Title, Text):
        # wrapper for QMessageBox Info
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle(Title)
        msg.setText(Text)
        msg.setStandardButtons(QMessageBox.Ok)
        return msg.exec_()

    def init(self, Window, DAQconfFile, ConfDir=None, WDname=None):
        # initialisation
        super().setupUi(Window)  # initialize base class
        self.Window = Window

        # set display options, fonts etc.
        self.setOptions()

        # set help
        self.setHelp_EN()

        # find user home directory and create directory 'PhyPi'
        self.homedir = os.getenv('HOME')
        if ConfDir is not None:
            if ConfDir[0] != '/':  # relative path
                self.ConfDir = self.homedir + '/' + ConfDir
            else:  # absolute path
                self.ConfDir = ConfDir
        else:
            if self.ConfDir is None:
                self.ConfDir = self.homedir + '/picoCosmo'
        if not os.path.exists(self.ConfDir):
            os.makedirs(self.ConfDir)

        # set initial working Directory
        if WDname is not None:
            if WDname[0] != '/':  # relative path
                self.WDname = self.homedir + '/' + WDname
            else:  # absolute path
                self.WDname = WDname
        else:
            if self.WDname is None:
                self.WDname = self.ConfDir
        if not os.path.exists(self.WDname):
            os.makedirs(self.WDname)

        self.lE_WorkDir.setText(self.WDname)

        # define actions
        self.pB_abort.clicked.connect(QtCore.QCoreApplication.instance().quit)
        self.rB_EditMode.clicked.connect(self.actionEditConfig)
        self.pB_StartRun.clicked.connect(self.actionStartRun)
        self.pB_FileSelect.clicked.connect(self.selectConfigFile)
        self.pB_WDselect.clicked.connect(self.selectWD)
        self.pB_Help.clicked.connect(self.setHelp_EN)
        self.pB_Hilfe.clicked.connect(self.setHelp_DE)
        self.pB_SaveConfig.clicked.connect(self.saveDefaultConfig)
        self.pB_ReloadConfig.clicked.connect(self.reloadConfigs)
        self.pB_loadPSconf.clicked.connect(self.loadPSconf)
        self.pB_loadBMconf.clicked.connect(self.loadBMconf)
        self.pB_loadPFconf.clicked.connect(self.loadPFconf)

        # initialization dependent on DAQ config file
        self.initDAQ(DAQconfFile)

    def readConfigs(self, DAQconfD):
        # read configuration options from file names in DAQ config dictionary

        if "DeviceFile" in DAQconfD:
            self.PSfile = DAQconfD["DeviceFile"]  # configuration file for scope
        else:
            self.MB_Warning('Warning', 'no device configuration file - exiting')
            print('     no device configuration file - exiting')
            exit(1)

        if "BMfile" in DAQconfD:
            self.BMfile = DAQconfD["BMfile"]  # Buffer Manager configuration file
        else:
            self.MB_Warning('Warning', 'no BM configuration file - exiting')
            print('     no BM configuration file - exiting')
            exit(1)

        if "PFfile" in DAQconfD:
            self.PFfile = DAQconfD["PFfile"]  # Buffer Manager configuration file
        else:
            self.MB_Warning('Warning', 'no pulse filter configuration file - exiting')
            print('     no pulse filter configuration file - exiting')
            exit(1)

        # display config data in GUI
        self.pTE_OsciConfig.setPlainText(open(self.iwd + '/' + self.PSfile, 'r').read())
        self.pTE_BMconfig.setPlainText(open(self.iwd + '/' + self.BMfile, 'r').read())
        self.pTE_PFconfig.setPlainText(open(self.iwd + '/' + self.PFfile, 'r').read())

    def initDAQ(self, DAQconfFile):
        # initialize DAQ from config files - need absolute path
        path = os.path.dirname(DAQconfFile)
        if path == '':
            path = '.'
        self.iwd = path  # initial working direcotory

        try:
            with open(DAQconfFile) as f:
                DAQconf = f.read()
                try:
                    DAQconfdict = yaml.load(DAQconf, Loader=yaml.Loader)
                except Exception:
                    # self.MB_Warning('Warning',
                    #   'DAQ configuration not valid yaml format ' + DAQconfFile +'\n' + str(e) )
                    print('     DAQ configuration not valid yaml format' + DAQconfFile)
                    # exit(1)
                    return
        except Exception:
            print('   DAQ configuration file ' + DAQconfFile + ' not found')
            # self.MB_Warning('Warning',
            #     'failed to read DAQ configuration file ' + DAQconfFile +'\n' + str(e))
            # exit(1)
            DAQconf = "missing !"
            return

        self.lE_DAQConfFile.setText(DAQconfFile)
        RunTag = os.path.split(DAQconfFile)[1].split('.')[0]
        self.lE_RunTag.setText(RunTag)

        print('   - DAQ configuration from file ' + DAQconfFile)

        # display config in GUI
        self.pTE_DAQConfig.setPlainText(DAQconf)
        # read configurations from DAQ dictionary
        self.readConfigs(DAQconfdict)

    # - end iniDAQ

    def reloadConfigs(self):
        # reload configs after edits in DAQconfig
        # retrieve actual configuration from GUI
        DAQconf = self.pTE_DAQConfig.toPlainText()
        # check if valid yaml syntax
        try:
            DAQconfDict = yaml.load(DAQconf, Loader=yaml.Loader)
        except Exception as e:
            self.MB_Warning('Warning', 'DAQ config is not valid yaml format \n' + str(e))
            return 1
        self.readConfigs(DAQconfDict)

    def setOptions(self):
        # set font for plainTextEdit to monospace
        monofont = QtGui.QFont()
        monofont.setStyleHint(QtGui.QFont.TypeWriter)
        monofont.setFamily("unexistentfont")
        self.pTE_OsciConfig.setFont(monofont)
        self.pTE_BMconfig.setFont(monofont)
        self.pTE_PFconfig.setFont(monofont)
        # no line wrap, horizontal scroll bar instead
        self.pTE_OsciConfig.setLineWrapMode(0)
        self.pTE_BMconfig.setLineWrapMode(0)
        self.pTE_PFconfig.setLineWrapMode(0)

    def setHelp_DE(self):
        self.TE_Help.setText(open('doc/Hilfe.html', 'r').read())

    def setHelp_EN(self):
        self.TE_Help.setText(open('doc/help.html', 'r').read())

    def loadPSconf(self):
        path2File = QtWidgets.QFileDialog.getOpenFileName(None, 'Picoscope config', self.iwd, 'yaml(*.yaml)')
        FileName = str(path2File[0]).strip()
        if FileName != '':
            # print('selected File ' + str(FileName) )
            self.pTE_OsciConfig.setPlainText(open(FileName, 'r').read())

    def loadBMconf(self):
        path2File = QtWidgets.QFileDialog.getOpenFileName(None, 'Buffer Manager config', self.iwd, 'yaml(*.yaml)')
        FileName = str(path2File[0]).strip()
        if FileName != '':
            # print('selected File ' + str(FileName) )
            self.pTE_BMconfig.setPlainText(open(FileName, 'r').read())

    def loadPFconf(self):
        path2File = QtWidgets.QFileDialog.getOpenFileName(None, 'Pulse Filter config', self.iwd, 'yaml(*.yaml)')
        FileName = str(path2File[0]).strip()
        if FileName != '':
            # print('selected File ' + str(FileName) )
            self.pTE_PFconfig.setPlainText(open(FileName, 'r').read())

    def selectConfigFile(self):
        path2File = QtWidgets.QFileDialog.getOpenFileName(
            parent=None, caption='DAQ config', directory=self.WDname, filter='DAQ(*.daq)'
        )
        FileName = str(path2File[0]).strip()
        if FileName != '':
            # print('selected File ' + str(FileName) )
            self.initDAQ(FileName)

    def selectWD(self):
        path2WD = QtWidgets.QFileDialog.getExistingDirectory(None, '~')
        WDname = str(path2WD).strip()
        if WDname != '':
            # print('selected Directory' + WDname )
            self.lE_WorkDir.setText(WDname)
            self.WDname = WDname

    def actionEditConfig(self):
        checked = self.rB_EditMode.isChecked()
        self.pTE_DAQConfig.setReadOnly(not checked)
        self.pB_ReloadConfig.setEnabled(checked)
        self.pTE_OsciConfig.setReadOnly(not checked)
        self.pTE_BMconfig.setReadOnly(not checked)
        self.pTE_PFconfig.setReadOnly(not checked)

    def saveConfigs(self, confdir, DAQfile=None, verbose=1):
        # save configuration in ~/picoCosmo

        # retrieve actual configuration from GUI
        DAQconf = self.pTE_DAQConfig.toPlainText()
        # check if valid yaml syntax
        try:
            DAQconfDict = yaml.load(DAQconf, Loader=yaml.Loader)
        except Exception as e:
            self.MB_Warning('Warning', 'DAQ config is not valid yaml format \n' + str(e))
            return 1

        PSconf = self.pTE_OsciConfig.toPlainText()
        # check if valid yaml syntax
        try:
            _ = yaml.load(PSconf, Loader=yaml.Loader)
        except Exception as e:
            self.MB_Warning('Warning', 'PicoScope config is not valid yaml format\n' + str(e))
            return 1

        BMconf = self.pTE_BMconfig.toPlainText()
        try:
            _d = yaml.load(BMconf, Loader=yaml.Loader)
        except Exception as e:
            self.MB_Warning('Warning', 'Buffer Manager config is not valid yaml format \n' + str(e))
            return 1

        PFconf = self.pTE_PFconfig.toPlainText()
        try:
            _d = yaml.load(PFconf, Loader=yaml.Loader)
        except Exception as e:
            self.MB_Warning('Warning', 'Pulse Filter config is not valid yaml format \n' + str(e))
            return 1

        # generate file name from RunTag if not given explicitely
        if (DAQfile) is None:
            DAQfile = str(self.lE_RunTag.text()).replace(' ', '') + '.daq'
        fullDAQfile = confdir + '/' + DAQfile
        if verbose:
            if self.MB_Question('Question', 'saving Config to file ' + fullDAQfile) == QMessageBox.Cancel:
                return 1
        try:
            fDAQ = open(fullDAQfile, 'w')
            print(DAQconf, file=fDAQ)
            self.DAQfile = DAQfile
            fDAQ.close()
        except Exception as e:
            self.MB_Warning('Warning', 'Failed to save ' + fullDAQfile + '\n' + str(e))
            return 1

        # store all other configuration files

        fnam = DAQconfDict['DeviceFile']
        dir, fn = os.path.split(fnam)
        if dir != '':
            if not os.path.exists(confdir + '/' + dir):
                os.makedirs(confdir + '/' + dir)
        try:
            fPS = open(confdir + '/' + fnam, 'w')
            print(PSconf, file=fPS)
            fPS.close()
        except Exception as e:
            self.MB_Warning('Warning', 'Failed to store ' + fnam + ' in directory ' + confdir + '\n' + str(e))
            return 1

        fnam = DAQconfDict['BMfile']
        dir, fn = os.path.split(fnam)
        if dir != '':
            if not os.path.exists(confdir + '/' + dir):
                os.makedirs(confdir + '/' + dir)
        try:
            fBM = open(confdir + '/' + fnam, 'w')
            print(BMconf, file=fBM)
            fBM.close()
        except Exception as e:
            self.MB_Warning('Warning', 'Failed to store ' + fnam + ' in directory ' + confdir + '\n' + str(e))
            return 1

        fnam = DAQconfDict['PFfile']
        dir, fn = os.path.split(fnam)
        if dir != '':
            if not os.path.exists(confdir + '/' + dir):
                os.makedirs(confdir + '/' + dir)
        try:
            fPF = open(confdir + '/' + fnam, 'w')
            print(PFconf, file=fPF)
            fPF.close()
        except Exception as e:
            self.MB_Warning('Warning', 'Failed to store ' + fnam + ' in directory ' + confdir + '\n' + str(e))
            return 1

        return 0

    def saveDefaultConfig(self):
        # save configuration
        # propose name for DAQ configuration file from RunTag
        _file = self.ConfDir + '/' + str(self.lE_RunTag.text()).replace(' ', '') + '.daq'
        # select file and direcotory
        path2File = QtWidgets.QFileDialog.getSaveFileName(None, 'save configuration as', _file, 'daq(*.daq)')
        fullDAQfile = str(path2File[0]).strip()
        if fullDAQfile != '':
            # remember new config directory
            self.ConfDir = os.path.dirname(fullDAQfile)
            DAQfile = os.path.basename(fullDAQfile)
        else:
            print("   abort - no file name given")
            return 1
        # set name and save all configs
        self.lE_DAQConfFile.setText(fullDAQfile)
        if self.saveConfigs(self.ConfDir, DAQfile=DAQfile, verbose=0):
            print("   !!! failed to save configuration files")
            return 1
        else:
            print("   - configuration files stored in directory " + self.ConfDir)
            return 0

    def saveEnvironment(self):
        '''
        Save picoCosmo configuration to file ~/CONFIG_ENVIRONMENT_file
        '''
        hd = os.getenv('HOME')
        # ... and find name of work directory
        cfgname = hd + '/' + CONFIG_ENVIRONMENT_file
        fcfg = open(cfgname, 'w')
        print('work_directory: ', self.WDname, file=fcfg)
        print('config_directory: ', self.ConfDir, file=fcfg)
        print('daq_file: ', os.path.basename(self.DAQfile), file=fcfg)

    def actionStartRun(self):
        # start script runCosmo.py in subdirectory

        # time stamp for this run
        datetime = time.strftime('%y%m%d-%H%M', time.localtime())
        RunTag = str(self.lE_RunTag.text()).replace(' ', '')

        # generate config files for new run in dedicated subdirectory
        self.runDir = RunTag + '_' + datetime
        self.path_to_WD = self.WDname + '/' + self.runDir
        if not os.path.exists(self.path_to_WD):
            os.makedirs(self.path_to_WD)
        # store config in working directory
        if self.saveConfigs(self.path_to_WD):
            print("   !!! failed to save Config files for this run")
            return
        else:
            print("   - files for this run stored in directory " + self.path_to_WD)

        # save changes to picocosmo environment
        self.saveEnvironment()

        # close GUI window and start runCosmo
        print('\n*==* CosmoGui: closing window and starting runCosmo.py')
        #      self.Window.close()
        self.Window.hide()

        # start runCosmo
        self.start_runCosmo()

        # exit or continue ?
        if self.MB_YesNo('End Dialog', 'Exit picoCosmo ? ') == QMessageBox.Yes:
            QtCore.QCoreApplication.instance().quit()
            print('*==* picoCosmo: exit \n')
        else:
            self.Window.show()

    def start_runCosmo(self):
        _CosmoDir = os.getcwd()
        subprocess.run(
            args=[sys.executable, '-m', 'picocosmo.runCosmoDAQ', self.DAQfile], cwd=self.WDname + '/' + self.runDir
        )


# - end Class Ui_CosmoWindow


def runCosmoUi():
    script = sys.argv[0]
    print('\n*==* ' + script + ' running \n')

    # get relevant paths
    path_to_picoCosmo = os.path.dirname(script)
    if path_to_picoCosmo == '':
        path_to_picoCosmo = './'  # python2 work-around
    homedir = os.getenv('HOME')
    # ... and find name of work directory
    cfgname = homedir + '/' + CONFIG_ENVIRONMENT_file
    try:
        with open(cfgname) as cfg:
            cfg_dict = yaml.load(cfg, Loader=yaml.Loader)
    except Exception:
        cfgname = path_to_picoCosmo + '/' + CONFIG_ENVIRONMENT_file
        try:
            with open(cfgname) as cfg:
                cfg_dict = yaml.load(cfg, Loader=yaml.Loader)
        except Exception:
            print(2 * ' ', ' !!! no valid file ' + CONFIG_ENVIRONMENT_file + ' found - using defaults')
            cfg_dict = {}
            cfg_dict['work_directory'] = '~'
            cfg_dict['config_directory'] = ' '
            cfg_dict['daq_file'] = 'default.daq'

    work_directory = cfg_dict['work_directory']
    if work_directory == '~':
        work_directory = homedir
    if work_directory == '.':
        work_directory = os.getcwd()
    conf_directory = cfg_dict['config_directory']
    if conf_directory == '~':
        conf_directory = homedir
    if conf_directory == '.':
        conf_directory = os.getcwd()
    if conf_directory == ' ':
        conf_directory = path_to_picoCosmo
    DAQconfFile = conf_directory + '/' + cfg_dict['daq_file']

    # check for / read command line arguments
    # get DAQ configuration file
    if len(sys.argv) == 2:
        if sys.argv[1] != '':
            DAQconfFile = os.path.abspath(sys.argv[1])  # with full path to file
            print(DAQconfFile)

    # start GUI
    if path_to_picoCosmo != '':
        os.chdir(path_to_picoCosmo)  # change path to where CosmoGui lives
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = ComoGuiUiInterface()
    ui.init(MainWindow, DAQconfFile, ConfDir=conf_directory, WDname=work_directory)

    # start pyqt event loop
    MainWindow.show()
    sys.exit(app.exec_())


if __name__ == "__main__":  # - - - - - - - - - - - - - - - - - - - -
    runCosmoUi()
