#!D:/IDE/anaconda3/envs/Microscope_Scan python
# -*- coding: UTF-8 -*-
"""
@FileName：SerialPortUtil.py
@Description：
@Author：XSH
@Time：2025/12/8 20:11
@Department：研发部
@Copyright：©2025 乐辰生物
"""
from PyQt5                  import Qt
from AppSettings.Settings   import *
from AppContexts.Contexts   import SerialContext
from AppCore.Requests       import SerialRequest
from AppUtils.AppUtils      import AppUtils
from AppUtils.AppConvert    import AppConvert
CACHE_SIZE        : int = 512
MICROSCOPE_READ_WAITTIMES: int = 150
SERIAL_PORT_SEND_TIMEOUT: int = 2
SERIAL_PORT_READ_TIMEOUT: int = 2
MICROSCOPE_SEND_TIMEOUT : int = 50
MICROSCOPE_READ_TIMEOUT : int = 4
MAX_MICROSCOPE_READ_TIMEOUT : int = 60000
class SerialPortUtil:
    @staticmethod
    def isValidSerialPort(Manager: Qt.QSerialPort) -> bool:
        if not isinstance(Manager, Qt.QSerialPort) or not Manager.isOpen():
            return False
        error = Manager.error()
        if error == Qt.QSerialPort.SerialPortError.NoError                    :
            return True         #
        if error == Qt.QSerialPort.SerialPortError.DeviceNotFoundError        :
            return False        #
        if error == Qt.QSerialPort.SerialPortError.PermissionError            :
            return True         #
        if error == Qt.QSerialPort.SerialPortError.OpenError                  :
            return True         #
        if error == Qt.QSerialPort.SerialPortError.ParityError                :
            return True         #
        if error == Qt.QSerialPort.SerialPortError.FramingError               :
            return True         #
        if error == Qt.QSerialPort.SerialPortError.BreakConditionError        :
            return True         #
        if error == Qt.QSerialPort.SerialPortError.WriteError                 :
            return True         #
        if error == Qt.QSerialPort.SerialPortError.ReadError                  :
            return True         #
        if error == Qt.QSerialPort.SerialPortError.ResourceError              :
            return False        #
        if error == Qt.QSerialPort.SerialPortError.UnsupportedOperationError  :
            return False        #
        if error == Qt.QSerialPort.SerialPortError.TimeoutError               :
            return True         #
        if error == Qt.QSerialPort.SerialPortError.NotOpenError               :
            return False        #
        if error == Qt.QSerialPort.SerialPortError.UnknownError               :
            return False        #
        return True

    @staticmethod
    def WaitMicroscopeDone(Manager: Qt.QSerialPort, Command: int, *args, **kwargs) -> SerialRequest:
        request: SerialRequest = None
        finishWaittimes: int = 2
        command: str
        args_str: str = ''
        if isinstance(args, (tuple, list)) and len(args) > 0:
            for value in args:
                args_str += f' {value}'

        command = f'{Command}' + args_str
        request: SerialRequest = SerialRequest.MicroscopeRequest(command, Manager)
        request.ResultNumber = RECODE_FAILURE
        isWrong: int = SerialPortUtil.SendByteArray(Manager, Qt.QByteArray(request.TransmitBinary))
        request.ResultNumber = isWrong
        if RECODE_SUCCEED != isWrong:
            isWrong = SerialPortUtil.SendByteArray(Manager, Qt.QByteArray(request.TransmitBinary))
            request.ResultNumber = isWrong
        if RECODE_SUCCEED != isWrong:
            request.ResultNumber = isWrong
            request.ReceivedBinary = b''
        else:
            timeout: int = kwargs.get('timeout', None)
            ReceivedBuffer: Qt.QByteArray
            Qt.QThread.msleep(finishWaittimes)
            if not isinstance(timeout, (int, float)):
                max_waittimes = MICROSCOPE_READ_WAITTIMES
            else:
                max_waittimes: int = min(MAX_MICROSCOPE_READ_TIMEOUT, max(MICROSCOPE_READ_TIMEOUT, int(kwargs.get('timeout', MICROSCOPE_READ_TIMEOUT))))

            isWrong, ReceivedBuffer = SerialPortUtil.ReadTargetByteArray(Manager, Qt.QByteArray(request.ReceivedTails), int(max_waittimes))
            if RECODE_SUCCEED != isWrong:
                request.ReceivedBinary = b''
                isWrong, ReceivedBuffer = SerialPortUtil.ReadTargetByteArray(Manager, Qt.QByteArray(request.ReceivedTails),int(max_waittimes))
            request.ResultNumber = isWrong
            request.ReceivedBinary = Qt.QByteArray(ReceivedBuffer)
            # Manager.clear()
            # Manager.flush()

        Qt.QThread.msleep(finishWaittimes)
        return request if RECODE_SUCCEED == isWrong else None

    @staticmethod
    def read_empty(Manager: Qt.QSerialPort, timeout: int = 100) -> SerialRequest:
        ReceivedBuffer: Qt.QByteArray
        request: SerialRequest = SerialRequest.MicroscopeRequest(0, Manager)
        waittimes: int = int(timeout) if isinstance(timeout, int) else int(request.ReceivedWaits)
        SerialPortUtil.WaitForReadyRead(Manager, MICROSCOPE_READ_TIMEOUT)
        isWrong, ReceivedBuffer = SerialPortUtil.ReadTargetByteArray(Manager, Qt.QByteArray(request.ReceivedTails), waittimes)
        request.ReceivedBinary = Qt.QByteArray(ReceivedBuffer) if RECODE_SUCCEED == isWrong else Qt.QByteArray()
        request.ResultNumber = isWrong
        print(request.ReceivedBinary)
        return request if RECODE_SUCCEED == isWrong else None

    @staticmethod
    def parserReceived(request: SerialRequest, types: list[type]) -> list:
        result: list = None
        if not isinstance(request, SerialRequest) or RECODE_SUCCEED != request.ResultNumber:
            return result
        ByteArray: Qt.QByteArray = Qt.QByteArray(request.ReceivedBinary)
        buffer: str = ByteArray.data().decode('utf-8')
        buffer = buffer.replace('$', '')
        results: list[str] = buffer.split('\r')
        result_list: list
        type_length: int = len(types) if isinstance(types, (tuple, list)) else 0
        result = []
        for string_data in results:
            if '$' in string_data or len(string_data) <= 0:
                result_list = []
            else:
                result_list = string_data.split(' ')

            for index, data in enumerate(result_list):
                if 0 <= index < type_length:
                    try:
                        result.append(AppConvert.ObjectConvert(data, types[index]))
                    except Exception as e:
                        print(e)
                        result.append(data)
                else:
                    result.append(data)

        return result

    @staticmethod
    def OpenSerialPort(Manager: Qt.QSerialPort,Parameter: SerialContext) -> int:
        isWrong: int = RECODE_FAILURE
        if not isinstance(Manager, Qt.QSerialPort) or not isinstance(Parameter, SerialContext):
            return isWrong
        try:
            if SerialPortUtil.IsSerialPortOpen(Manager):
                SerialPortUtil.CloseSerialPort(Manager, Parameter)
            Manager.close()
            Parameter   .IsOpen = False
            Manager     .setPortName    (Parameter.COM          )
            Manager     .setBaudRate    (Parameter.BaudRate     )
            Manager     .setDataBits    (Parameter.DataBit      )
            Manager     .setParity      (Parameter.ParityBit    )
            Manager     .setStopBits    (Parameter.StopBit      )
            Manager     .setFlowControl (Parameter.FlowControl  )
            Manager     .open(Qt.QIODevice.ReadWrite)
            Parameter.IsOpen = Manager.isOpen()
            isWrong = RECODE_SUCCEED if Parameter.IsOpen else RECODE_FAILURE
        except Exception as e:
            AppUtils.print_Exception(e)

        return isWrong

    @staticmethod
    def CloseSerialPort(Manager: Qt.QSerialPort, Parameter: SerialContext) -> int:
        isWrong: int = RECODE_FAILURE
        if not isinstance(Manager, Qt.QSerialPort) or not isinstance(Parameter, SerialContext):
            return isWrong
        try:
            Parameter.IsOpen = Manager.isOpen()
            Manager.close()
            Parameter.IsOpen = Manager.isOpen()
            isWrong = RECODE_SUCCEED if Parameter.IsOpen == False else RECODE_FAILURE
        except Exception as e:
            AppUtils.print_Exception(e)
        return isWrong

    @staticmethod
    def hasManager(Manager: Qt.QSerialPort) -> bool:
        return isinstance(Manager, Qt.QSerialPort)

    @staticmethod
    def hasParameter(Parameter: SerialContext):
        return isinstance(Parameter, SerialContext)

    @staticmethod
    def hasRequest(request: SerialRequest):
        return isinstance(request, SerialRequest)

    @staticmethod
    def SendByteArray(Manager: Qt.QSerialPort, data: (bytes, bytearray, Qt.QByteArray), timeout: int = None) -> int:
        isWrong             : int = RECODE_FAILURE
        max_length          : int
        process_length      : int
        remaining_length    : int
        process_times       : Qt.QDateTime
        process_data        : Qt.QByteArray
        waittimes           : int = int(timeout) if isinstance(timeout, (int, float)) else 5000

        global  CACHE_SIZE
        if not SerialPortUtil.isWritable(Manager):
            return isWrong
        if isinstance(data, Qt.QByteArray):
            process_data = data
        elif isinstance(data, (bytes, bytearray)):
            process_data = Qt.QByteArray(data)
        elif isinstance(data, str):
            process_data = Qt.QByteArray(data.encode('utf-8'))
        else:
            return isWrong
        max_length          = process_data.length()
        process_length      = 0
        remaining_length    = max_length
        process_times       = Qt.QDateTime.currentDateTime().addMSecs( waittimes)
        if remaining_length <= CACHE_SIZE:
            while Qt.QDateTime.currentDateTime() < process_times:
                process_length = Manager.write(process_data)
                process_length = 0 if process_length <= 0 else process_length
                remaining_length = remaining_length - process_length
                SerialPortUtil.WaitForBytesWritten(Manager, SERIAL_PORT_SEND_TIMEOUT)
                if Manager.bytesToWrite() <= 0:
                    remaining_length = 0
                    break
                if remaining_length <= 0:
                    break
        else:
            while Qt.QDateTime.currentDateTime() < process_times:
                while remaining_length > 0:
                    process_length = Manager.write(process_data.mid(process_length, CACHE_SIZE))
                    process_length = 0 if process_length <= 0 else process_length
                    remaining_length = remaining_length - CACHE_SIZE
                    SerialPortUtil.WaitForBytesWritten(Manager, SERIAL_PORT_SEND_TIMEOUT)
                    if Qt.QDateTime.currentDateTime() > process_times:
                        break
                    if remaining_length <= 0:
                        break
        Manager.clearError()
        isWrong = RECODE_SUCCEED if remaining_length <= 0 else RECODE_FAILURE
        return isWrong


    @staticmethod
    def ReadByteArray(Manager: Qt.QSerialPort, timeout: int = None) -> (int, Qt.QByteArray):
        isWrong             : int = RECODE_FAILURE
        max_length          : int
        process_length      : int
        remaining_length    : int
        process_times       : Qt.QDateTime
        process_data        : Qt.QByteArray = Qt.QByteArray()
        read_length         : int
        read_data           : Qt.QByteArray
        waittimes           : int = int(timeout) if isinstance(timeout, (int, float)) else 1000
        if not SerialPortUtil.isReadable(Manager):
            return isWrong, process_data
        global  CACHE_SIZE
        process_times       = Qt.QDateTime.currentDateTime().addMSecs( waittimes)
        while Qt.QDateTime.currentDateTime() < process_times:
            remaining_length  = SerialPortUtil.bytesAvailable(Manager)
            if remaining_length <= 0:
                SerialPortUtil.WaitForReadyRead(Manager, SERIAL_PORT_READ_TIMEOUT)
                continue
            while remaining_length > 0:
                read_data = Manager.read(remaining_length)
                if read_data is not None and len(target) > 0:
                    process_data.append(read_data)
                    process_length = len(target)
                else:
                    process_length = 0
                remaining_length = remaining_length - process_length
                if Qt.QDateTime.currentDateTime() > process_times:
                    break
                if remaining_length <= 0:
                    break
            if Qt.QDateTime.currentDateTime() > process_times:
                break
        isWrong = RECODE_SUCCEED if not process_data.isEmpty() else RECODE_FAILURE
        Manager.flush()
        Manager.clear()
        Manager.clearError()
        return isWrong, process_data

    @staticmethod
    def ReadTargetByteArray(Manager: Qt.QSerialPort, target: Qt.QByteArray, timeout: int = None) -> (int, Qt.QByteArray):
        if not isinstance(target, (bytes, bytearray, Qt.QByteArray)) or len(target) <= 0:
            return SerialPortUtil.ReadByteArray(Manager, timeout)
        isWrong             : int = RECODE_FAILURE
        max_length          : int
        process_length      : int
        remaining_length    : int
        process_times       : Qt.QDateTime
        process_data        : Qt.QByteArray = Qt.QByteArray()
        read_length         : int
        read_data           : Qt.QByteArray
        waittimes           : int = int(timeout) if isinstance(timeout, (int, float)) else 1000
        atEnd               : bool = False
        index               : int
        target_length       : int = Qt.QByteArray(target).length()
        if not SerialPortUtil.isReadable(Manager):
            return isWrong, process_data
        global  CACHE_SIZE
        process_times       = Qt.QDateTime.currentDateTime().addMSecs( waittimes)
        while Qt.QDateTime.currentDateTime() < process_times:
            remaining_length  = SerialPortUtil.bytesAvailable(Manager)
            if remaining_length <= 0:
                SerialPortUtil.WaitForReadyRead(Manager, SERIAL_PORT_READ_TIMEOUT)
                continue
            while remaining_length > 0:
                read_data = Manager.read(remaining_length)
                if read_data is not None and len(target) > 0:
                    process_data.append(read_data)
                    process_length = len(target)
                    try:
                        index = process_data.indexOf(Qt.QByteArray(target))
                    except Exception as e:
                        AppUtils.print_Exception(e)
                        index = -1
                    atEnd = index >= 0
                    if atEnd:
                        break
                else:
                    process_length = 0
                remaining_length = remaining_length - process_length
                if Qt.QDateTime.currentDateTime() > process_times:
                    break
                if atEnd:
                    break
            if atEnd:
                break
            if Qt.QDateTime.currentDateTime() > process_times:
                break
        if atEnd:
            try:
                index = process_data.indexOf(Qt.QByteArray(target))
                process_data = process_data.mid(0, index + target_length)
            except Exception as e:
                AppUtils.print_Exception(e)
        isWrong = RECODE_SUCCEED if not process_data.isEmpty() else RECODE_FAILURE
        Manager.clearError()
        return isWrong, process_data

    @staticmethod
    def SendRequest(Manager: Qt.QSerialPort, request: SerialRequest) -> int:
        isWrong = RECODE_FAILURE
        if not SerialPortUtil.hasManager(Manager) or not SerialPortUtil.hasRequest(request):
            return isWrong
        if not request.hasTransmitBinary:
            return isWrong
        isWrong = SerialPortUtil.SendByteArray(Manager, request.TransmitBinary)
        return isWrong

    @staticmethod
    def ReceiveRequest(Manager: Qt.QSerialPort, request: SerialRequest) -> int:
        isWrong: int = RECODE_FAILURE
        if not SerialPortUtil.hasManager(Manager) or not SerialPortUtil.hasRequest(request):
            return isWrong
        if not request.hasReceivedBinary:
            return isWrong
        timeout: int = request.ReceivedWaits if request.hasReceivedWaits else None
        if not request.hasReceivedTails:
            isWrong, data = SerialPortUtil.ReadByteArray(Manager, timeout)
        else:
            isWrong, data = SerialPortUtil.ReadTargetByteArray(Manager, request.ReceivedTails, timeout)
        request.ReceivedBinary = data
        return isWrong



    @staticmethod
    def isWritable(Manager: Qt.QSerialPort) -> bool:
        return isinstance(Manager, Qt.QSerialPort) and Manager.isOpen() and Manager.isWritable()

    @staticmethod
    def isReadable(Manager: Qt.QSerialPort) -> bool:
        return isinstance(Manager, Qt.QSerialPort) and Manager.isOpen() and Manager.isReadable()

    @staticmethod
    def bytesAvailable(Manager: Qt.QSerialPort) -> int:
        return Manager.bytesAvailable() if isinstance(Manager, Qt.QSerialPort) and Manager.isOpen() else 0

    @staticmethod
    def bytesToWrite(Manager: Qt.QSerialPort) -> int:
        return Manager.bytesToWrite() if isinstance(Manager, Qt.QSerialPort) and Manager.isOpen() else 0



    @staticmethod
    def WaitForReadyRead(Manager: Qt.QSerialPort, timeout: int = None) -> bool:
        waittimes: int = int(timeout) if isinstance(timeout, (int, float)) else 1
        waittimes = min(30000, max(1, waittimes))
        return Manager.waitForReadyRead(waittimes)

    @staticmethod
    def WaitForBytesWritten(Manager: Qt.QSerialPort, timeout: int = None) -> bool:
        waittimes: int = int(timeout) if isinstance(timeout, (int, float)) else 1
        waittimes = min(30000, max(1, waittimes))
        return Manager.waitForBytesWritten(waittimes)

    @staticmethod
    def GetSerialPortError(Manager: Qt.QSerialPort) -> Qt.QSerialPort.SerialPortError:
        status: int = Qt.QSerialPort.SerialPortError.DeviceNotFoundError
        if isinstance(Manager, Qt.QSerialPort) and Manager.isOpen():
            status = Manager.error()
        return status


    @staticmethod
    def IsSerialPortOpen(Manager: Qt.QSerialPort) -> bool:
        status: bool = False
        if isinstance(Manager, Qt.QSerialPort):
            status = Manager.isOpen()
        return status

    @staticmethod
    def IsSerialPortClosed(Manager: Qt.QSerialPort) -> bool:
        status: bool = True
        if isinstance(Manager, Qt.QSerialPort):
            status = not Manager.isOpen()
        return status

    @staticmethod
    def IsSerialPortError(Manager: Qt.QSerialPort) -> bool:
        status: bool
        if not isinstance(Manager, Qt.QSerialPort) or not Manager.isOpen():
            status = True
        else:
            status = not (Manager.error() == Qt.QSerialPort.SerialPortError.NoError)
        return status



