class Serafin:
    """!
    @brief A Serafin object corresponds to a single Serafin in file IO stream

    # Attributes:
    - language <str>: language for variables ('fr' or 'en')
    - mode <str>: file open mode
    - file <_io.BufferedReader>: input Serafin stream
    - filename <str>: path to file
    - file_size <int>: size of file
    """
    def __init__(self, filename, mode, language):
        self.language = language
        self.mode = mode

        self.file = None  # will be opened when called using `with` statement
        self.filename = filename
        self.file_size = None

    def __enter__(self):
        self.file = open(self.filename, self.mode)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()
        return False class LoadMeshDialog(OutputProgressDialog):
    def __init__(self, mesh_type, input_header):
        super().__init__('Processing the mesh. Please wait.', 'Processing the mesh...')
        thread = ConstructIndexThread(mesh_type, input_header)
        self.connectToThread(thread)

    def outputFinished(self):
        self.setValue(100)
        self.setCancelButtonText('OK')
        logging.info('Finished processing the mesh')

    def cancel(self):
        if self.cancelButton.text() == 'Cancel':
            if not self.thread.canceled:
                self.thread.canceled = True
                logging.info('Input canceled.')

    def run(self):
        self.thread.run()
        if not self.thread.canceled:
            self.outputFinished()
        self.exec_()
        return self.thread.mesh class OutputProgressDialog(QProgressDialog):
    def __init__(self, message='Output in progress', title='Writing the output...', parent=None):
        super().__init__(message, 'OK', 0, 100, parent)
        self.setMinimumDuration(0)

        self.cancelButton = QPushButton('Cancel')
        self.setCancelButton(self.cancelButton)
        self.canceled.connect(self.cancel)

        self.setAutoReset(False)
        self.setAutoClose(False)

        self.setWindowTitle(title)
        self.setWindowFlags(Qt.WindowTitleHint)
        self.setFixedSize(300, 150)
        self.thread = None

        self.open()
        self.setValue(0)
        QApplication.processEvents()

    def outputFinished(self):
        self.setValue(100)
        self.setCancelButtonText('OK')
        logging.info('Finished writing the output.')

    def connectToThread(self, thread):
        self.thread = thread
        thread.tick.connect(self.setValue)

    def cancel(self):
        if self.cancelButton.text() == 'Cancel':
            if not self.thread.canceled:
                self.thread.canceled = True
                logging.info('Output canceled.') class OutputThread(QThread):
    tick = pyqtSignal(int, name='changed')

    def __init__(self):
        super().__init__()
        self.canceled = False class ProgressBarIterator:
    def __init__(self, iterable, emit=lambda x: x, perc_range=(0, 100), length=None):
        """
        Update progress bar during an iterative
        @param iterable: iterator
        @param perc_range <(int, int)>: minimum and maximum percentage bounds
        @param emit: function to call at each significant progress
        """
        self.emit = emit
        self.iterable = iterable
        self.min, self.max = perc_range
        self.length = length

    def do_at_next(self, perc):
        """
        Call self.emit function with curent percentage
        @param perc <float>: percentage progress (within range [0, 100])
        """
        self.emit(perc)
        QApplication.processEvents()

    def __iter__(self):
        """Iterate and update progress bar at each new percentage"""
        length = len(self.iterable) if self.length is None else self.length
        if length != 0:
            ratio = (self.max - self.min) / length
            old_perc = -1
            for i, value in enumerate(self.iterable):
                yield value

                new_perc = int(self.min + (i + 1) * ratio)
                if new_perc > old_perc:
                    self.do_at_next(new_perc)
                old_perc = new_perc

    @staticmethod
    def prepare(*args, **kwargs):
        """
        Get a ready sub-class on which to iterate
        @return <SubProgressBarIterator>:  ProgressBarIterator sub-class with pre-defined arguments
        """
        class SubProgressBarIterator(ProgressBarIterator):
            def __init__(self, iterable, unit=None):
                """!
                @param iterable: iterable or generator to loop over
                @param unit: argument not used in GUI
                """
                super().__init__(iterable, *args, **kwargs)
        return SubProgressBarIterator class PyTelToolWidget(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.input = None
        if parent is None:
            self.language = settings.LANG
            self.csv_separator = settings.CSV_SEPARATOR
            self.fmt_float = settings.FMT_FLOAT
            self.logging_level = settings.LOGGING_LEVEL
        else:
            self.language = parent.language
            self.csv_separator = parent.csv_separator
            self.fmt_float = parent.fmt_float
            self.logging_level = parent.logging_level
        self.setMinimumWidth(600)
        self.setWindowFlags(self.windowFlags() | Qt.CustomizeWindowHint)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

    def inDialog(self):
        if self.parent is not None:
            self.parent.inDialog()
        else:
            self.setWindowFlags(self.windowFlags() & ~Qt.WindowCloseButtonHint)
            self.setEnabled(False)
            self.show()

    def outDialog(self):
        if self.parent is not None:
            self.parent.outDialog()
        else:
            self.setWindowFlags(self.windowFlags() | Qt.WindowCloseButtonHint)
            self.setEnabled(True)
            self.show()

    def switch_language(self, language):
        if language == 'fr':
            self.input.frenchButton.setChecked(True)
        else:
            self.input.englishButton.setChecked(True) class QPlainTextEditLogger(logging.Handler):
    """!
    @brief A text edit box displaying the message logs
    """
    def __init__(self, parent):
        super().__init__()
        self.widget = QPlainTextEdit(parent)
        self.widget.setReadOnly(True)

    def emit(self, record):
        msg = self.format(record)
        self.widget.appendPlainText(msg) def save_dialog(file_format, input_name='', input_names=None):
    # create the save file dialog
    options = QFileDialog.Options()
    options |= QFileDialog.DontUseNativeDialog
    options |= QFileDialog.DontConfirmOverwrite
    if file_format == 'Serafin':
        extensions = settings.SERAFIN_EXT
        file_filter = 'Serafin Files (%s)' % ' '.join(['*%s' % extension for extension in extensions])
    elif file_format == 'CSV':
        extensions = ['.csv']
        file_filter = 'CSV Files (*.csv)'
    else:
        raise NotImplementedError('File format %s is not supported' % file_format)
    filename, _ = QFileDialog.getSaveFileName(None, 'Choose the output file name', '', file_filter, options=options)

    # check the file name consistency
    if not filename:
        return True, ''

    # add default extension (taken as first) if missing
    if os.path.splitext(filename)[1] not in extensions:
        filename += extensions[0]

    # overwrite to the input file is forbidden
    if input_name:
        if filename == input_name:
            QMessageBox.critical(None, 'Error', 'Cannot overwrite to the input file.', QMessageBox.Ok)
            return True, ''
    elif input_names:
        for name in input_names:
            if filename == name:
                QMessageBox.critical(None, 'Error', 'Cannot overwrite to the input file.', QMessageBox.Ok)
                return True, ''

    # handle overwrite manually
    overwrite = handle_overwrite(filename)
    if overwrite is None:
        return True, ''

    return False, filename class SerafinInputTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent

        # create the button open
        self.btnOpen = QPushButton('Open', self, icon=self.style().standardIcon(QStyle.SP_DialogOpenButton))
        self.btnOpen.setToolTip('<b>Open</b> a Serafin file')
        self.btnOpen.setFixedSize(105, 50)

        # create some text fields displaying the IO files info
        self.inNameBox = QLineEdit()
        self.inNameBox.setReadOnly(True)
        self.summaryTextBox = QPlainTextEdit()
        self.summaryTextBox.setFixedHeight(50)
        self.summaryTextBox.setReadOnly(True)

        # create a checkbox for language selection
        self.langBox = QGroupBox('Input language')
        hlayout = QHBoxLayout()
        self.frenchButton = QRadioButton('French')
        self.englishButton = QRadioButton('English')
        hlayout.addWidget(self.frenchButton)
        hlayout.addWidget(self.englishButton)
        self.langBox.setLayout(hlayout)
        self.langBox.setMaximumHeight(80)
        if self.parent.language == 'fr':
            self.frenchButton.setChecked(True)
        else:
            self.englishButton.setChecked(True)

        # create the widget displaying message logs
        self.logTextBox = QPlainTextEditLogger(self)
        self.logTextBox.setFormatter(logging.Formatter(settings.LOGGING_FMT_GUI))
        logging.getLogger().addHandler(self.logTextBox)
        logging.getLogger().setLevel(self.parent.logging_level)

        self.input_layout = QVBoxLayout()
        hlayout = QHBoxLayout()
        hlayout.setAlignment(Qt.AlignLeft)
        hlayout.addItem(QSpacerItem(50, 1))
        hlayout.addWidget(self.btnOpen)
        hlayout.addItem(QSpacerItem(30, 1))
        hlayout.addWidget(self.langBox)
        self.input_layout.addLayout(hlayout)
        self.input_layout.addItem(QSpacerItem(10, 10))

        glayout = QGridLayout()
        glayout.addWidget(QLabel('Input file'), 1, 1)
        glayout.addWidget(self.inNameBox, 1, 2)
        glayout.addWidget(QLabel('Summary'), 2, 1)
        glayout.addWidget(self.summaryTextBox, 2, 2)
        glayout.setAlignment(Qt.AlignLeft)
        glayout.setSpacing(10)
        self.input_layout.addLayout(glayout)

    def current_language(self):
        if self.frenchButton.isChecked():
            return 'fr'
        return 'en'

    def reset(self):
        self.inNameBox.clear()
        self.summaryTextBox.clear()

    def open_event(self):
        filename, _ = QFileDialog.getOpenFileName(self, 'Open a Serafin file', '', 'Serafin Files (%s)' %
                                                  ' '.join(['*%s' % extension for extension in settings.SERAFIN_EXT]),
                                                  options=QFileDialog.Options() | QFileDialog.DontUseNativeDialog)
        if not filename:
            return True, ''

        if not test_open(filename):
            return True, ''

        return False, filename

    def read(self, filename):
        self.inNameBox.setText(filename)
        data = SerafinData('', filename, self.current_language())
        try:
            data.read()
        except (Serafin.SerafinRequestError, Serafin.SerafinValidationError) as e:
            QMessageBox.critical(None, 'Serafin Error', e.message, QMessageBox.Ok, QMessageBox.Ok)
            return False, None
        except PermissionError:
            QMessageBox.critical(None, 'Permission denied',
                                 'Permission denied. (Is the file opened by another application?).',
                                 QMessageBox.Ok, QMessageBox.Ok)
            return False, None
        self.summaryTextBox.appendPlainText(data.header.summary())
        logging.info('Finished reading the input file')
        return True, data

    def read_2d(self, filename, update=True):
        if update:
            self.inNameBox.setText(filename)
        data = SerafinData('', filename, self.current_language())

        try:
            is_2d = data.read()
        except (Serafin.SerafinRequestError, Serafin.SerafinValidationError) as e:
            QMessageBox.critical(None, 'Serafin Error', e.message, QMessageBox.Ok, QMessageBox.Ok)
            return False, None
        except PermissionError:
            QMessageBox.critical(None, 'Permission denied',
                                 'Permission denied. (Is the file opened by another application?).',
                                 QMessageBox.Ok, QMessageBox.Ok)
            return False, None

        if not is_2d:
            QMessageBox.critical(self, 'Error', 'The file type (TELEMAC 3D) is currently not supported.',
                                 QMessageBox.Ok)
            return False, None
        if update:
            self.summaryTextBox.appendPlainText(data.header.summary())
        logging.info('Finished reading the input file')
        return True, data class VariableTable(TableWidgetDragRows):
    def __init__(self):
        super().__init__()
        self.setColumnCount(3)
        self.setHorizontalHeaderLabels(['ID', 'Name', 'Unit'])
        vh = self.verticalHeader()
        vh.setSectionResizeMode(QHeaderView.Fixed)
        vh.setDefaultSectionSize(20)
        hh = self.horizontalHeader()
        hh.setDefaultSectionSize(110)
        self.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.setMaximumHeight(800)

    def fill(self, header):
        for i, (var_id, name, unit) in enumerate(zip(header.var_IDs, header.var_names, header.var_units)):
            self.insertRow(self.rowCount())
            id_item = QTableWidgetItem(var_id.strip())
            name_item = QTableWidgetItem(name.decode(Serafin.SLF_EIT).strip())
            unit_item = QTableWidgetItem(unit.decode(Serafin.SLF_EIT).strip())
            self.setItem(i, 0, id_item)
            self.setItem(i, 1, name_item)
            self.setItem(i, 2, unit_item)

    def get_selected(self):
        selected = []
        for i in range(self.rowCount()):
            selected.append(self.item(i, 0).text())
        return selected

    def get_selected_all(self):
        selected = []
        for i in range(self.rowCount()):
            selected.append((self.item(i, 0).text(),
                            bytes(self.item(i, 1).text(), Serafin.SLF_EIT).ljust(16),
                            bytes(self.item(i, 2).text(), Serafin.SLF_EIT).ljust(16)))
        return selected
import datetime
import logging
import sys
import pyteltools.slf.misc as operations
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QApplication, QCheckBox, QComboBox, QGridLayout, QHBoxLayout,
                             QLabel, QLineEdit, QMessageBox, QPlainTextEdit, QPushButton,
                             QSpacerItem, QStyle, QTableWidgetItem, QTabWidget, QVBoxLayout, QWidget)
from pyteltools.conf import settings
from pyteltools.slf import Serafin
from .util import LoadMeshDialog, OutputProgressDialog, OutputThread, ProgressBarIterator, PyTelToolWidget, \
    QPlainTextEditLogger, save_dialog, SerafinInputTab, VariableTable
        self._reinitCommonFrames()

        if not self.common_frames:
            QMessageBox.critical(self, 'Error', 'No common frame with file A.', QMessageBox.Ok)
            self.second_data = None
            return

        # record the mesh
        self.parent.inDialog()
        meshLoader = LoadMeshDialog('interpolation', self.second_data.header)
        self.second_mesh = meshLoader.run()

        # locate all points of the first mesh in the second mesh
        self.is_inside, self.point_interpolators \
            = self.second_mesh.get_point_interpolators(list(zip(self.first_data.header.x, self.first_data.header.y)))
        self.parent.outDialog()
        if meshLoader.thread.canceled:
            self.second_data = None
            return

        # update the file summary
        self.secondSummaryTextBox.appendPlainText(self.second_data.header.summary())
        self.parent.getSecond(False, common_vars)


class SubmitTab(QWidget):
    def __init__(self, inputTab, parent):
        super().__init__()
        self.input = inputTab
        self.parent = parent

        self._initWidgets()
        self._bindEvents()
        self._setLayout()

    def _initWidgets(self):
        # create a text field for mesh intersection info display
        self.infoBox = QPlainTextEdit()
        self.infoBox.setFixedHeight(60)
        self.infoBox.setReadOnly(True)

        # create two 3-column tables for variables selection
        self.firstTable = VariableTable()
        self.secondTable = VariableTable()

        # create combo box widgets for choosing the operation
        self.operationBox = QComboBox()
        self.operationBox.setFixedSize(400, 30)
        for op in ['Project B on A', 'B - A', 'A - B', 'max(A, B)', 'min(A, B)']:
            self.operationBox.addItem(op)

        # create the widget displaying message logs
        self.logTextBox = QPlainTextEditLogger(self)
        self.logTextBox.setFormatter(logging.Formatter(settings.LOGGING_FMT_GUI))
        logging.getLogger().addHandler(self.logTextBox)
        logging.getLogger().setLevel(self.parent.logging_level)

        # create a check box for output file format (simple or double precision)
        self.singlePrecisionBox = QCheckBox('Convert to SERAFIN \n(single precision)', self)
        self.singlePrecisionBox.setEnabled(False)

        # create the submit button
        self.btnSubmit = QPushButton('Submit', self, icon=self.style().standardIcon(QStyle.SP_DialogSaveButton))
        self.btnSubmit.setToolTip('<b>Submit</b> to write a Serafin output')
        self.btnSubmit.setFixedSize(105, 50)

    def _bindEvents(self):
        self.btnSubmit.clicked.connect(self.btnSubmitEvent)

    def _setLayout(self):
        mainLayout = QVBoxLayout()
        mainLayout.addItem(QSpacerItem(1, 10))
        mainLayout.addWidget(self.infoBox)
        mainLayout.addItem(QSpacerItem(1, 15))
        hlayout = QHBoxLayout()
        hlayout.addItem(QSpacerItem(30, 1))

        vlayout = QVBoxLayout()
        lb = QLabel('Available variables')
        vlayout.addWidget(lb)
        vlayout.setAlignment(lb, Qt.AlignHCenter)
        vlayout.addWidget(self.firstTable)
        hlayout.addLayout(vlayout)
        hlayout.addItem(QSpacerItem(15, 1))

        vlayout = QVBoxLayout()
        lb = QLabel('Output variables')
        vlayout.addWidget(lb)
        vlayout.setAlignment(lb, Qt.AlignHCenter)
        vlayout.addWidget(self.secondTable)

        hlayout.addLayout(vlayout)
        hlayout.addItem(QSpacerItem(30, 1))

        mainLayout.addLayout(hlayout)
        mainLayout.addItem(QSpacerItem(50, 20))
        glayout = QGridLayout()
        glayout.addWidget(QLabel('     Select an operation'), 1, 1)
        glayout.addWidget(self.operationBox, 1, 2)
        glayout.setSpacing(10)
        mainLayout.addLayout(glayout)
        mainLayout.setAlignment(glayout, Qt.AlignTop | Qt.AlignLeft)
        mainLayout.addItem(QSpacerItem(50, 20))

        hlayout = QHBoxLayout()
        hlayout.addItem(QSpacerItem(50, 10))
        hlayout.addWidget(self.btnSubmit)
        hlayout.addItem(QSpacerItem(50, 10))
        hlayout.addWidget(self.singlePrecisionBox)
        hlayout.addItem(QSpacerItem(50, 10))
        mainLayout.addLayout(hlayout)
        mainLayout.addItem(QSpacerItem(30, 15))
        mainLayout.addWidget(QLabel('   Message logs'))
        mainLayout.addWidget(self.logTextBox.widget)
        self.setLayout(mainLayout)

    def _initVarTables(self, common_vars):
        for i, (var_ID, var_name, var_unit) in enumerate(common_vars):
            self.firstTable.insertRow(self.firstTable.rowCount())
            id_item = QTableWidgetItem(var_ID.strip())

            name_item = QTableWidgetItem(var_name.decode(Serafin.SLF_EIT).strip())