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

'''
Copyright (C) 2011-2015 German Aerospace Center DLR
(Deutsches Zentrum fuer Luft- und Raumfahrt e.V.),
Institute of System Dynamics and Control
All rights reserved.

This file is part of PySimulator.

PySimulator is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

PySimulator 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with PySimulator. If not, see www.gnu.org/licenses.
'''

from PySide import QtGui, QtCore
import threading
import types

import os
import math
import gc
import time

from Plugins.Simulator import SimulatorBase


class IntegratorControl(QtGui.QDialog):
    ''' Class for the Integrator Control GUI '''

    ''' Signals of the class  '''
    resultsUpdated = QtCore.Signal(types.StringType)
    reallyFinished = QtCore.Signal()
    SimulationFinished = QtCore.Signal(types.BooleanType)

    def __init__(self, parent, models=None):
        self.models = models
        self.parent = parent

        QtGui.QDialog.__init__(self, parent)
        self.setWindowTitle("Integrator Control")

        _mainGrid = QtGui.QGridLayout(self)

        _numIn = QtGui.QGroupBox("Numerical Integration", self)
        _mainGrid.addWidget(_numIn, 0, 0)
        _numInLayout = QtGui.QGridLayout()
        _numIn.setLayout(_numInLayout)

        _numInLayout.addWidget(QtGui.QLabel("Time in s:"), 0, 0)
        self.timeFrom = QtGui.QLineEdit(self)
        self.timeFrom.setValidator(QtGui.QDoubleValidator(-1e9, 1e9, 15, self))
        _numInLayout.addWidget(self.timeFrom, 0, 1)
        _numInLayout.addWidget(QtGui.QLabel("      to"), 0, 2)
        self.timeTo = QtGui.QLineEdit(self)
        self.timeTo.setValidator(QtGui.QDoubleValidator(-1e9, 1e9, 15, self))
        _numInLayout.addWidget(self.timeTo, 0, 3)

        _numInLayout.addWidget(QtGui.QLabel("Algorithm:"), 1, 0)
        self.algorithm = QtGui.QComboBox(self)
        _numInLayout.addWidget(self.algorithm, 1, 1, 1, 3)

        _numInLayout.addWidget(QtGui.QLabel("Error tolerance: "), 2, 0)
        self.errorTol = QtGui.QLineEdit(self)
        _errorValid = QtGui.QDoubleValidator(0, 10, 20, self)
        _errorValid.setNotation(QtGui.QDoubleValidator.ScientificNotation)
        self.errorTol.setValidator(_errorValid)
        _numInLayout.addWidget(self.errorTol, 2, 1)

        _numInLayout.addWidget(QtGui.QLabel("Step size: "), 2, 2)
        self.stepSize = QtGui.QLineEdit(self)
        _stepSizeValid = QtGui.QDoubleValidator(0, 10, 20, self)
        _stepSizeValid.setNotation(QtGui.QDoubleValidator.ScientificNotation)
        self.stepSize.setValidator(_stepSizeValid)
        self.stepSize.setEnabled(False)
        _numInLayout.addWidget(self.stepSize, 2, 3)

        _results = QtGui.QGroupBox("Results", self)
        _mainGrid.addWidget(_results, 1, 0)
        _resultsLayout = QtGui.QGridLayout()
        _results.setLayout(_resultsLayout)

        self.inTime = QtGui.QRadioButton("Equidistant grid points in time", self)
        self.inTime.setChecked(True)
        _resultsLayout.addWidget(self.inTime, 0, 0, 1, 2)
        self.inTimeVal = QtGui.QLineEdit(self)
        self.inTimeVal.setValidator(QtGui.QDoubleValidator(2, 1.e9, 0, self))
        _resultsLayout.addWidget(self.inTimeVal, 0, 2)

        self.perTime = QtGui.QRadioButton("Width of equidistant time grid", self)
        _resultsLayout.addWidget(self.perTime, 1, 0, 1, 2)
        self.perTimeVal = QtGui.QLineEdit(self)
        self.perTimeVal.setValidator(QtGui.QDoubleValidator(0, 1.e9, 15, self))
        _resultsLayout.addWidget(self.perTimeVal, 1, 2)
        self.useIntegratorGrid = QtGui.QRadioButton("Use integrator steps for grid points", self)
        _resultsLayout.addWidget(self.useIntegratorGrid, 2, 0, 1, 3)
        _resultsLayout.addItem(QtGui.QSpacerItem(0, 30))

        self.plot = QtGui.QCheckBox("Plot online during numerical integration", self)
        _resultsLayout.addWidget(self.plot, 3, 0, 1, 3)

        saveFile = QtGui.QLabel("Save results in:", self)
        _resultsLayout.addWidget(saveFile, 4, 0, QtCore.Qt.AlignRight)
        _browseSaveFile = QtGui.QPushButton("Select", self)
        _resultsLayout.addWidget(_browseSaveFile, 4, 3)
        self.saveFilePath = QtGui.QLineEdit("", self)
        _resultsLayout.addWidget(self.saveFilePath, 4, 1, 1, 2)

        _control = QtGui.QGroupBox("Control", self)
        _mainGrid.addWidget(_control, 2, 0)
        _controlLayout = QtGui.QGridLayout()
        _control.setLayout(_controlLayout)

        self.run = QtGui.QPushButton("Run", self)
        _controlLayout.addWidget(self.run, 0, 0)
        self.stop = QtGui.QPushButton("Stop", self)
        _controlLayout.addWidget(self.stop, 0, 1)
        self.closebutton = QtGui.QPushButton("Close", self)
        _controlLayout.addWidget(self.closebutton, 0, 2)
        self._duplicateModelCheck = QtGui.QCheckBox("Duplicate model after simulation", self)
        _controlLayout.addWidget(self._duplicateModelCheck, 1, 0, 1, 2)

        _simulationInfo = QtGui.QGroupBox("Simulation info", self)
        _mainGrid.addWidget(_simulationInfo, 3, 0)
        _simulationInfoLayout = QtGui.QGridLayout()
        _simulationInfo.setLayout(_simulationInfoLayout)

        label = QtGui.QLabel('Model name:')
        label.setAlignment(QtCore.Qt.AlignRight)
        _simulationInfoLayout.addWidget(label, 0, 0)
        label = QtGui.QLabel('Current time:')
        label.setAlignment(QtCore.Qt.AlignRight)
        _simulationInfoLayout.addWidget(label, 1, 0)
        label = QtGui.QLabel('Time events:')
        label.setAlignment(QtCore.Qt.AlignRight)
        _simulationInfoLayout.addWidget(label, 2, 0)
        label = QtGui.QLabel('State events:')
        label.setAlignment(QtCore.Qt.AlignRight)
        _simulationInfoLayout.addWidget(label, 3, 0)
        label = QtGui.QLabel('Result points:')
        label.setAlignment(QtCore.Qt.AlignRight)
        _simulationInfoLayout.addWidget(label, 4, 0)
        label = QtGui.QLabel('Result file size:')
        label.setAlignment(QtCore.Qt.AlignRight)
        _simulationInfoLayout.addWidget(label, 5, 0)
        label = QtGui.QLabel('Result file name:')
        label.setAlignment(QtCore.Qt.AlignRight)
        _simulationInfoLayout.addWidget(label, 6, 0)
        label = QtGui.QLabel('Elapsed real time:')
        label.setAlignment(QtCore.Qt.AlignRight)
        _simulationInfoLayout.addWidget(label, 7, 0)

        self.currentModelLabel = QtGui.QLabel('')
        _simulationInfoLayout.addWidget(self.currentModelLabel, 0, 1, 1, 3)
        self.showedTimeLabel = QtGui.QLabel('')
        _simulationInfoLayout.addWidget(self.showedTimeLabel, 1, 1)
        self.currentTimeProgress = QtGui.QProgressBar()
        self.currentTimeProgress.setRange(0, 100)
        self.currentTimeProgress.setMaximumHeight(13)
        _simulationInfoLayout.addWidget(self.currentTimeProgress, 1, 2, 1, 2)
        self.showedTimeEvents = QtGui.QLabel('')
        _simulationInfoLayout.addWidget(self.showedTimeEvents, 2, 1, 1, 3)
        self.showedStateEvents = QtGui.QLabel('')
        _simulationInfoLayout.addWidget(self.showedStateEvents, 3, 1, 1, 3)
        self.showedGridPoints = QtGui.QLabel('')
        _simulationInfoLayout.addWidget(self.showedGridPoints, 4, 1, 1, 3)
        self.filesizeLabel = QtGui.QLabel('')
        _simulationInfoLayout.addWidget(self.filesizeLabel, 5, 1, 1, 3)
        self.realResultFileNameEdit = QtGui.QLineEdit('', self)
        self.realResultFileNameEdit.setFrame(False)
        self.realResultFileNameEdit.setReadOnly(True)
        self.realResultFileNameEdit.setMaximumHeight(13)
        pal = self.realResultFileNameEdit.palette()
        pal.setColor(self.realResultFileNameEdit.backgroundRole(), QtCore.Qt.transparent)
        self.realResultFileNameEdit.setPalette(pal)
        _simulationInfoLayout.addWidget(self.realResultFileNameEdit, 6, 1, 1, 3)
        self.realtimeLabel = QtGui.QLabel('')
        _simulationInfoLayout.addWidget(self.realtimeLabel, 7, 1, 1, 3)

        self.run.setFocus()

        def _resultTypeChanged():
            if self.inTime.isChecked():
                self.inTimeVal.setEnabled(True)
                self.perTimeVal.setEnabled(False)
            if self.perTime.isChecked():
                self.inTimeVal.setEnabled(False)
                self.perTimeVal.setEnabled(True)
            if self.useIntegratorGrid.isChecked():
                self.inTimeVal.setEnabled(False)
                self.perTimeVal.setEnabled(False)

        def _plotOnlineChanged():
            self.models[self.currentNumberedModelName].integrationSettings.plotOnline_isChecked = self.plot.isChecked()

        def _browseSaveFileDo():
            (fileName, trash) = QtGui.QFileDialog().getSaveFileName(self, 'Save results', os.getcwd(), '*.' + self.models[self.currentNumberedModelName].integrationSettings.resultFileExtension)
            if fileName != '':
                self.saveFilePath.setText(fileName)

        self.algorithm.currentIndexChanged.connect(self._algoChanged)
        self.inTime.toggled.connect(_resultTypeChanged)
        self.perTime.toggled.connect(_resultTypeChanged)
        self.useIntegratorGrid.toggled.connect(_resultTypeChanged)

        _browseSaveFile.clicked.connect(_browseSaveFileDo)

        self.run.clicked.connect(self._simulate)
        self.stop.clicked.connect(self._stopSimulation)
        self.closebutton.clicked.connect(self._close)

        self.stop.setEnabled(False)
        self.currentNumberedModelName = None
        self.changeCurrentModel()
        self.parent.nvb.currentModelChanged.connect(self.changeCurrentModel)

        if self.models[self.currentNumberedModelName].integrationResults.canLoadPartialData:
            self.plot.setCheckState(QtCore.Qt.Checked)
        else:
            self.plot.setEnabled(False)
            self.plot.setCheckState(QtCore.Qt.Unchecked)
        self.plot.toggled.connect(_plotOnlineChanged)

        self.SimulationFinished.connect(self.triggerdResultUpdate)
        

    def _algoChanged(self, item):
        if self.models[self.currentNumberedModelName].getIntegrationAlgorithmHasFixedStepSize(self.algorithm.currentText()):
            self.errorTol.setEnabled(False)
            self.stepSize.setEnabled(True)
        else:
            self.errorTol.setEnabled(True)
            self.stepSize.setEnabled(False)
        if self.models[self.currentNumberedModelName].getIntegrationAlgorithmCanProvideStepSizeResults(self.algorithm.currentText()):
            self.useIntegratorGrid.setEnabled(True)
        else:
            if self.useIntegratorGrid.isChecked():
                self.inTime.setChecked(True)
            self.useIntegratorGrid.setEnabled(False)

    def reject(self):
        ''' Overload the standard reject function to not close the GUI
            when ESC is pressed '''
        pass

    def closeEvent(self, event):
        # print "Close event"
        if not self.closebutton.isEnabled():
            # Prevent closing the GUI when a simulation is running
            event.ignore()
        else:
            self.parent.nvb.currentModelChanged.disconnect(self.changeCurrentModel)
            self.reallyFinished.emit()

    def changeCurrentModel(self):
        # print "Change current model in IntegratorControl"
        # Only change the current model if it is not being simulated
        if self.currentNumberedModelName is not None:
            if not self.models[self.currentNumberedModelName].integrationStatistics.finished:
                return
        # Get the tree item of the new selected model
        item = self.parent.nvb.currentModelItem
        if item is None:
            # There is no current model in the variables browser (no model loaded)
            # Close the GUI
            self._close()
            return
        modelName = item.text(0)
        if modelName == self.currentNumberedModelName:
            return

        # Before changing the current model, save the integrator settings to the model
        self._setSettingsFromIntegratorControlGUI(self.currentNumberedModelName)
        self.currentNumberedModelName = modelName
        if self.models[modelName].modelType == 'None':
            self._close()
            return
        self._setIntegratorControlGUIFromSettings(self.currentNumberedModelName)

    def _close(self):
        self._setSettingsFromIntegratorControlGUI(self.currentNumberedModelName)
        self.close()

    def _setSettingsFromIntegratorControlGUI(self, modelName):
        ''' Stores the settings of the GUI into the model  '''
        if modelName is None:
            return
        if modelName == '':
            return
        if modelName not in self.models.keys():
            return
        model = self.models[modelName]
        model.integrationSettings.startTime = float(self.timeFrom.text())
        model.integrationSettings.stopTime = float(self.timeTo.text())
        model.integrationSettings.algorithmName = str(self.algorithm.currentText())
        model.integrationSettings.errorToleranceRel = float(self.errorTol.text())
        model.integrationSettings.fixedStepSize = float(self.stepSize.text())
        model.integrationSettings.gridPoints = int(float(self.inTimeVal.text()))
        model.integrationSettings.gridWidth = float(self.perTimeVal.text())
        if self.inTime.isChecked():
            model.integrationSettings.gridPointsMode = 'NumberOf'
        elif self.perTime.isChecked():
            model.integrationSettings.gridPointsMode = 'Width'
        else:
            model.integrationSettings.gridPointsMode = 'Integrator'
        model.integrationSettings.resultFileName = self.saveFilePath.text()
        model.integrationSettings.plotOnline_isChecked = self.plot.isChecked()
        model.integrationSettings.duplicateModel_isChecked = self._duplicateModelCheck.isChecked()

    def _setIntegratorControlGUIFromSettings(self, modelName):
        ''' Shows the settings of the model in the GUI  '''
        model = self.models[modelName]

        self.timeFrom.setText(str(model.integrationSettings.startTime))
        self.timeTo.setText(str(model.integrationSettings.stopTime))
        self.algorithm.currentIndexChanged.disconnect()
        self.algorithm.clear()
        self._itemList = model.getAvailableIntegrationAlgorithms()
        self.algorithm.currentIndexChanged.connect(self._algoChanged)
        self.algorithm.addItems(self._itemList)        
        self.algorithm.setCurrentIndex(self._itemList.index(model.integrationSettings.algorithmName))
        self.errorTol.setText(str(model.integrationSettings.errorToleranceRel))
        self.stepSize.setText(str(model.integrationSettings.fixedStepSize))
        self.inTimeVal.setText(str(model.integrationSettings.gridPoints))
        self.useIntegratorGrid.setEnabled(model.getIntegrationAlgorithmCanProvideStepSizeResults(model.integrationSettings.algorithmName))
        if model.integrationSettings.gridPointsMode == 'NumberOf':
            self.inTime.setChecked(True)
            self.inTimeVal.setEnabled(True)
            self.perTimeVal.setEnabled(False)
        elif model.integrationSettings.gridPointsMode == 'Width':
            self.perTime.setChecked(True)
            self.inTimeVal.setEnabled(False)
            self.perTimeVal.setEnabled(True)
        else:
            self.useIntegratorGrid.setChecked(True)
            self.inTimeVal.setEnabled(False)
            self.perTimeVal.setEnabled(False)
        self.perTimeVal.setText(str(model.integrationSettings.gridWidth))
        self.saveFilePath.setText(model.integrationSettings.resultFileName)
        self.plot.setEnabled(self.models[self.currentNumberedModelName].integrationResults.canLoadPartialData)
        self.plot.setCheckState(QtCore.Qt.Checked if model.integrationSettings.plotOnline_isChecked else QtCore.Qt.Unchecked)
        self._duplicateModelCheck.setCheckState(QtCore.Qt.Checked if model.integrationSettings.duplicateModel_isChecked else QtCore.Qt.Unchecked)
        self.currentModelLabel.setText(modelName)
        self.showTimeLabelAndProgress(model.integrationSettings, model.integrationStatistics, True)
        self.showedTimeEvents.setText(str(model.integrationStatistics.nTimeEvents) if model.integrationStatistics.nTimeEvents is not None else '')
        self.showedStateEvents.setText(str(model.integrationStatistics.nStateEvents) if model.integrationStatistics.nStateEvents is not None else '')
        self.showedGridPoints.setText(str(model.integrationStatistics.nGridPoints) if model.integrationStatistics.nGridPoints is not None else '')
        self.filesizeLabel.setText(self.fileSize2str(model.integrationResults.fileSize()))
        self.realResultFileNameEdit.setText(model.integrationResults.fileName)

    def fileSize2str(self, size):
        if size is not None:
            if size > 1024:
                return format(size / 1024, '0.2f') + ' GB'
            else:
                return format(size, '0.2f') + ' MB'
        else:
            return ''

    def showTimeLabelAndProgress(self, settings, statistics, lastCall=False):
        ''' Shows the current time and corresponding progress bar in simulation info '''
        # Current Time
        if statistics.reachedTime is None:
            currentTimeText = ''
        else:
            if lastCall:
                formatString = '0.14g'
                currentTimeText = format(statistics.reachedTime, formatString)
            else:
                # Current time: The number of showed digits depends on the difference
                #               to the last shown time. The effect is that you can
                #               see, if the simulation is slowing down in time
                #               but without displaying to much digits for fast sections.
                difference = statistics.reachedTime - self.lastCurrentTime
                self.lastCurrentTime = statistics.reachedTime
                if difference > 0.0:
                    nDigits = int(abs(min(0, math.log10(difference) - 1)))
                    formatString = '0.' + str(nDigits) + 'f'
                else:
                    nDigits = 14
                    formatString = '0.' + str(nDigits) + 'g'
                currentTimeText = format(math.floor(math.pow(10, nDigits) * statistics.reachedTime) / math.pow(10, nDigits), formatString)

            if len(currentTimeText) > 0:
                currentTimeText += ' s'
        self.showedTimeLabel.setText(currentTimeText)

        # Progress bar
        if statistics.reachedTime is None:
            progressBarTimeValue = settings.startTime
        else:
            progressBarTimeValue = statistics.reachedTime
            if lastCall:
                progressBarTimeValue = settings.stopTime

        a = settings.startTime
        b = settings.stopTime
        if b - a == 0.0:
            b = a + 1
        steps = (progressBarTimeValue - a) / (b - a) * (self.currentTimeProgress.maximum() - self.currentTimeProgress.minimum())
        self.currentTimeProgress.setValue(steps)
        # CPU time
        if hasattr(self, '_cpuStartTime'):
            statistics.cpuTime = time.clock() - self._cpuStartTime
        if statistics.cpuTime is not None:
            self.realtimeLabel.setText("%.2f s" % statistics.cpuTime)
        else:
            self.realtimeLabel.setText('')

    def _simulate(self):
        ''' Starts the simulation of the current model with the current settings in the GUI '''
        self.models[self.currentNumberedModelName].integrationStatistics.finished = False
        self.run.setEnabled(False)
        self.closebutton.setEnabled(False)
        # Delete pluginData because new simulation will start
        self.models[self.currentNumberedModelName].pluginData.clear()
        self._setSettingsFromIntegratorControlGUI(self.currentNumberedModelName)
        # Close the corresponding result file to have write access
        self.models[self.currentNumberedModelName].integrationResults.close()
        try:
            os.remove(self.models[self.currentNumberedModelName].integrationResults.fileName)
        except:
            pass

        self.models[self.currentNumberedModelName].integrationResultFileSemaphore = threading.Semaphore()
        if hasattr(self.models[self.currentNumberedModelName], 'integrationResults'):
            self.models[self.currentNumberedModelName].integrationResults.fileName = ''

        # Define some variables before simulation can start
        self.models[self.currentNumberedModelName].integrationStatistics.cpuTime = None
        self.models[self.currentNumberedModelName].integrationStatistics.nTimeEvents = 0
        self.models[self.currentNumberedModelName].integrationStatistics.nStateEvents = 0
        self.models[self.currentNumberedModelName].integrationStatistics.nGridPoints = 0
        self.models[self.currentNumberedModelName].integrationStatistics.reachedTime = self.models[self.currentNumberedModelName].integrationSettings.startTime

        # Define Timers for result updates and simulation info updates
        self.updateData = QtCore.QTimer()
        self.updateData.timeout.connect(self.triggerdResultUpdate)

        self.updateSimulationInfo = QtCore.QTimer()
        self.updateSimulationInfo.timeout.connect(self.showSimulationInfo)

        # Define a new thread for the simulation task
        self._simThread = simulationThread(self)

        self._simThread.model = self.models[self.currentNumberedModelName]
        self._simThread.model.simulationStopRequest = False

        self.lastCurrentTime = self._simThread.model.integrationStatistics.reachedTime
        self.showSimulationInfo()

        # Start the timers and the simulation thread
        self._simThread.SimulationFinished = self.SimulationFinished
        self.updateData.start(1000)
        self.updateSimulationInfo.start(500)
        self._cpuStartTime = time.clock()
        self.stop.setEnabled(True)
        self._simThread.start(QtCore.QThread.LowPriority)

    def showSimulationInfo(self, lastCall=False):
        ''' Shows the updated simulation information in the Integrator Control GUI '''

        model = self.models[self.currentNumberedModelName]
        # File size of result file
        fileSizeText = self.fileSize2str(model.integrationResults.fileSize())
        self.filesizeLabel.setText(fileSizeText)

        if not lastCall:
            model.integrationStatistics.reachedTime = model.getReachedSimulationTime()

        self.showTimeLabelAndProgress(model.integrationSettings, model.integrationStatistics, lastCall)

        # Number of events and grid points
        self.showedTimeEvents.setText(str(model.integrationStatistics.nTimeEvents))
        self.showedStateEvents.setText(str(model.integrationStatistics.nStateEvents))
        self.showedGridPoints.setText(str(model.integrationStatistics.nGridPoints))

        self.realResultFileNameEdit.setText(model.integrationResults.fileName)

    def _stopSimulation(self):
        ''' Stops the current simulation '''
        if '_simThread' in self.__dict__ and self._simThread.isRunning():
            print('try to stop integration ... ')
            self._simThread.model.simulationStopRequest = True

    def triggerdResultUpdate(self, lastCall=False):
        ''' This function is normally called when updated results shall be plotted '''
        if not self._simThread.isRunning() or lastCall:
            # The numerical integration is not running any more
            # Stop the timers
            self.updateData.stop()
            self.updateSimulationInfo.stop()
            # Close the result file to guarantee that all results are on file
            self.models[self.currentNumberedModelName].integrationResultFileSemaphore.acquire()
            self.models[self.currentNumberedModelName].integrationResults.close()
            self.models[self.currentNumberedModelName].integrationResultFileSemaphore.release()
            print("Results saved in " + self.models[self.currentNumberedModelName].integrationSettings.resultFileName + ".")

            # Re-open result file for further plotting
            self.models[self.currentNumberedModelName].integrationResultFileSemaphore.acquire()
            self.models[self.currentNumberedModelName].loadResultFile(self.models[self.currentNumberedModelName].integrationSettings.resultFileName)
            self.models[self.currentNumberedModelName].integrationResultFileSemaphore.release()

            # Show the correct simulation information
            self.showSimulationInfo(lastCall=True)
            if hasattr(self, '_cpuStartTime'):
                del(self._cpuStartTime)

            if self._simThread.model.simulationStopRequest:
                print("Integration stopped.")
            else:
                print("Integration completed.")

            self.resultsUpdated.emit(self.currentNumberedModelName)

            # Enable run and close buttons of Integrator menu
            self.run.setEnabled(True)
            self.closebutton.setEnabled(True)
            self.stop.setEnabled(False)

            # Duplicate model after simulation if selected
            if self._duplicateModelCheck.isChecked():
                self.parent.duplicateModel(self.currentNumberedModelName)

            self.models[self.currentNumberedModelName].integrationStatistics.finished = True

            # Check if the selection of the current model has changed during integration
            self.changeCurrentModel()
            gc.collect()

        else:
            # During integration: Send signal that results are updated, if selected and possible
            if self.models[self.currentNumberedModelName].integrationSettings.plotOnline_isChecked:
                self.resultsUpdated.emit(self.currentNumberedModelName)


class simulationThread(QtCore.QThread):
    ''' Class for the simulation thread '''
    def __init__(self, parent):
        super(simulationThread, self).__init__(parent)

    def run(self):
        haveCOM = False
        try:
            '''
            Do the numerical integration in a try branch
            to avoid losing the thread when an intended exception is raised
            '''
            try:
                import pydevd
                pydevd.connected = True
                pydevd.settrace(suspend=False)
            except:
                # do nothing, since error message only indicates we are not in debug mode
                pass
            try:
                import pythoncom
                pythoncom.CoInitialize()  # Initialize the COM library on the current thread
                haveCOM = True
            except:
                pass
            self.model.simulate()
        except SimulatorBase.Stopping:
            print("solver canceled ... ")
        except Exception, e:
            print("unexpected error ... ")
            print e
        finally:
            if haveCOM:
                try:
                    pythoncom.CoUninitialize()  # Close the COM library on the current thread
                except:
                    pass


        # Define simulation completed to stop updating plots and come back to the GUI
        self.SimulationFinished.emit(True)