class LeafletAssembly(QQuickItem):
    """Base class for all leaflet assembly types"""
    #  Q_CLASSINFO('DefaultProperty', 'leaflets')
    def __init__(self, parent=None):
        QQuickItem.__init__(self, parent)
        self._leaflets = []
        self._hwsoc = HWSOC()

    def componentComplete(self):
        QQuickItem.componentComplete(self)
        self.enableHWLink()

    leafletsChanged = pyqtSignal([QQmlListProperty], arguments=['leaflets'])

    @pyqtProperty(QQmlListProperty, notify=leafletsChanged)
    def leaflets(self):
        return QQmlListProperty(Leaflet, self, self._leaflets)

    def publishToHW(self, index=None):
        poslist = [lf.extension for lf in self._leaflets]
        logger.debug('publishing all to HW - [{}]'.format(', '.join(str(x) for x in poslist)))
        self._hwsoc.set_all_positions(poslist)

    @pyqtSlot()
    def enableHWLink(self):
        self.onLeafletReleased.connect(self.publishToHW)

    @pyqtSlot()
    def disableHWLink(self):
        self.onLeafletReleased.disconnect(self.publishToHW)
class LeafletAssembly(QQuickItem):
    """Base class for all leaflet assembly types"""

    #  Q_CLASSINFO('DefaultProperty', 'leaflets')

    def __init__(self, parent=None):
        QQuickItem.__init__(self, parent)
        self._leaflets = []
        self._hwsoc = HWSOC()
        self.hw_linked = False

    def componentComplete(self):
        QQuickItem.componentComplete(self)
        self.enableHWLink()

    leafletsChanged = pyqtSignal([QQmlListProperty], arguments=['leaflets'])

    @pyqtProperty(QQmlListProperty, notify=leafletsChanged)
    def leaflets(self):
        return QQmlListProperty(Leaflet, self, self._leaflets)

    @pyqtSlot()
    @pyqtSlot(int)
    def publishToHW(self, index=None):
        poslist = [lf.extension for lf in self._leaflets]
        logger.debug('publishing all to HW - [{}]'.format(', '.join(
            str(x) for x in poslist)))
        self._hwsoc.set_all_positions(poslist)

    @pyqtSlot()
    def setCalibration(self):
        self._hwsoc.set_calibration()

    @pyqtSlot()
    def enableHWLink(self):
        if not self.hw_linked:
            self.onLeafletReleased.connect(self.publishToHW)
            self.hw_linked = True

    @pyqtSlot()
    def disableHWLink(self):
        if self.hw_linked:
            self.onLeafletReleased.disconnect(self.publishToHW)
            self.hw_linked = False

    # pre-defined leaflet configurations
    @pyqtSlot()
    def setClosed(self):
        """Move leaflets to 'closed' position"""
        for ii, leaf in enumerate(self._leaflets):
            leaf.extension = leaf.max_extension if ii < 4 else 0
        self.publishToHW()

    @pyqtSlot()
    def setOpened(self):
        """Move leaflets to 'closed' position"""
        for ii, leaf in enumerate(self._leaflets):
            leaf.extension = 0
        self.publishToHW()
class TreatmentManager(QObject):
    def __init__(self, sequencelistmodel, parent=None, *args):
        super().__init__(parent=parent, *args)

        self.seqlist = sequencelistmodel
        self._hwsoc = HWSOC()
        self.mark = 0
        self._steps = 0
        self._sequence_cache = None

        # thread locks
        self.lock_running = QMutex()
        self.lock_waiting = QMutex()
        self.lock_waitcond = QWaitCondition()
        self.lock_steps = QMutex()

        # state variables
        self.state_paused = False
        self.state_running = False
        self.state_waitinghwok = False

        self.startTreatment.connect(self._startTreatment)
        self.stopTreatment.connect(self._stopTreatment)
        self.restartTreatment.connect(self._restartTreatment)
        self.abortTreatment.connect(self._abortTreatment)
        self.setHWOK.connect(self._sethwok)

        # create QThread and move this object to it
        self.thread = QThread()
        self.moveToThread(self.thread)
        self.thread.start()

    onTreatmentStarted = pyqtSignal()
    onTreatmentStopped = pyqtSignal(int)
    onTreatmentAborted = pyqtSignal(int)
    onTreatmentCompleted = pyqtSignal(int)
    onTreatmentAdvance = pyqtSignal(int)
    onTreatmentSkip = pyqtSignal(int, float)

    # cross-thread control via signals
    startTreatment = pyqtSignal([int])
    stopTreatment = pyqtSignal()
    restartTreatment = pyqtSignal()
    abortTreatment = pyqtSignal()
    setHWOK = pyqtSignal()

    onStepsChanged = pyqtSignal([int])

    @pyqtProperty(int, notify=onStepsChanged)
    def steps(self):
        self.lock_steps.lock()
        v = self._steps
        self.lock_steps.unlock()
        return v

    @steps.setter
    def steps(self, v):
        self.lock_steps.lock()
        self._steps = v
        self.lock_steps.unlock()

    onWaitingChanged = pyqtSignal([bool])

    @pyqtProperty(bool, notify=onWaitingChanged)
    def waitinghwok(self):
        self.lock_waiting.lock()
        v = self.state_waitinghwok
        self.lock_waiting.unlock()
        return v

    @waitinghwok.setter
    def waitinghwok(self, v):
        self.lock_waiting.lock()
        self.state_waitinghwok = v
        self.lock_waiting.unlock()

    onRunningChanged = pyqtSignal([bool])

    @pyqtProperty(bool, notify=onRunningChanged)
    def running(self):
        self.lock_running.lock()
        v = self.state_running
        self.lock_running.unlock()
        return v

    @running.setter
    def running(self, v):
        self.lock_running.lock()
        self.state_running = v
        self.lock_running.unlock()

    def deliverAll(self):
        while self.mark < len(self._sequence_cache) and self.running:
            duration = self.deliverOne()

            # advance to next segment?
            if self.running:
                if self.mark < len(self.seqlist) - 1:
                    self.mark += 1
                    self.steps += 1
                    #  if duration >= 1000:
                    #  self.onTreatmentAdvance.emit(self.mark) # only updates UI
                    self.onTreatmentAdvance.emit(self.mark)  # only updates UI
                else:
                    self._stopTreatment()
                    self.state_paused = False
                    self.onTreatmentCompleted.emit(self.mark)  #update ui

    def deliverOne(self):
        """Run timer for a single beam"""
        seg = self._sequence_cache[self.mark]
        extension_list = seg._members['extension_list'].value
        duration = float(seg._members['timecode_ms'].value)
        if duration <= 0:
            self.waitinghwok = True
            self.onTreatmentSkip.emit(self.mark, duration)
        else:
            self.waitinghwok = True
            self._hwsoc.set_all_positions(extension_list)
            while self.waitinghwok:
                # spin event loop until hwok signal is recieved after delivery of prev. segment
                QCoreApplication.processEvents()

            t1 = time.perf_counter()
            while (time.perf_counter() - t1) < duration * 0.001:
                # catch signals every 250ms while delivering
                if (time.perf_counter() - t1) % 0.25:
                    QCoreApplication.processEvents()
                if not self.state_running:
                    # early exit from UI
                    break
        return duration

    def _sethwok(self):
        self.waitinghwok = False

    @pyqtSlot(int)
    def _startTreatment(self, index):
        """Start the treatment at specified index"""
        self.mark = index
        self._sequence_cache = self.seqlist._items.copy()
        if not self.state_paused:
            self.steps = 1
        self.running = True
        self.onTreatmentStarted.emit()
        logger.debug("Treatment started")
        self.deliverAll()

    def _stopTreatment(self):
        self.state_paused = True
        self.running = False
        self.onTreatmentStopped.emit(self.mark)
        logger.debug("Treatment stopped")

    def _restartTreatment(self):
        self.steps = 0
        self.state_paused = False
        self._startTreatment(0)
        logger.debug("Treatment restarted")

    def _abortTreatment(self):
        self.running = False
        self.state_paused = False
        self.onTreatmentAborted.emit(self.mark)
        logger.debug("Treatment aborted")