# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.

"""
Serial port support for Windows.

Requires PySerial and pywin32.
"""

import win32file
import win32event
import win32con

# system imports
from serial.serialutil import to_bytes  # type: ignore[import]
from time import sleep

# twisted imports
from twisted.internet import abstract

# sibling imports
import logging
logger = logging.getLogger(__name__)


class SerialPort(abstract.FileDescriptor):

    """A serial device, acting as a transport, that uses a win32 event."""

    connected = 1

    def __init__(self, protocol, deviceName, reactor):
        self.initHard(protocol, deviceName, reactor)
        self.initSoft(protocol, deviceName, reactor)

    def initHard(self, protocol, deviceName, reactor):
        self.hComPort = win32file.CreateFile(
            deviceName,
            win32con.GENERIC_READ | win32con.GENERIC_WRITE,
            0,  # exclusive access
            None,  # no security
            win32con.OPEN_EXISTING,
            win32con.FILE_ATTRIBUTE_NORMAL | win32con.FILE_FLAG_OVERLAPPED,
            0)
        self.reactor = reactor
        self.protocol = protocol
        self.deviceName = deviceName
        self._overlappedRead = win32file.OVERLAPPED()
        self._overlappedRead.hEvent = win32event.CreateEvent(None, 1, 0, None)
        self._overlappedWrite = win32file.OVERLAPPED()
        self._overlappedWrite.hEvent = win32event.CreateEvent(None, 0, 0, None)
        self.reactor.addEvent(self._overlappedRead.hEvent, self, 'serialReadEvent')
        self.reactor.addEvent(self._overlappedWrite.hEvent, self, 'serialWriteEvent')       

    def initSoft(self, protocol, deviceName, reactor):
        self.outQueue = []
        self.closed = 0
        self.closedNotifies = 0
        self.writeInProgress = 0
        self.conneted = 1
        self._reconnInProgress = False
        self.protocol.makeConnection(self)
        self._startReading()

    def _startReading(self, len=4096):
        rc, self.read_buf = win32file.ReadFile(self.hComPort,
                                               win32file.AllocateReadBuffer(len),
                                               self._overlappedRead)

    def serialReadEvent(self):
#        logger.debug("serialReadEvent %s %s" % (self._overlappedRead.Internal, self._overlappedRead.InternalHigh))
        try:
            n = win32file.GetOverlappedResult(self.hComPort, self._overlappedRead, 1)
        except Exception as e:
            logger.debug("Exception %s" % e)
            sleep(10)
            logger.debug(self.connLost())
            n = 0
        if n > 0:
            # handle the received data:
            self.protocol.dataReceived(to_bytes(self.read_buf[:n]))
        # set up next read
        win32event.ResetEvent(self._overlappedRead.hEvent)
        self._startReading()

    def write(self, data):
        if data:
            if isinstance(data, str):
                data = str.encode(data)
            if self.writeInProgress:
                self.outQueue.append(data)
#                logger.debug("added to queue")
            else:
                self.writeInProgress = 1
                ret, n = win32file.WriteFile(self.hComPort, data, self._overlappedWrite)
#                logger.debug("Writed to file %s", ret)

    def serialWriteEvent(self):
#        logger.debug("serialWriteEvent %s %s" % (self._overlappedWrite.Internal, self._overlappedWrite.InternalHigh))
        if self._overlappedWrite.Internal < 0 and self._overlappedWrite.InternalHigh == 0 :   # DANGER: Not documented variables
            logger.debug(self.connLost())
            self.writeInProgress = 0
            return
        try:
            dataToWrite = self.outQueue.pop(0)
        except IndexError:
            self.writeInProgress = 0
            return
        else:
            win32file.WriteFile(self.hComPort, dataToWrite, self._overlappedWrite)

    def connLost(self):
        if self._reconnInProgress:
            return None
        self._reconnInProgress = True
        return self.reactor.callLater(30, self.connectionLostEvent, self)

    def connectionLostEvent(self, reason):
        abstract.FileDescriptor.connectionLost(self, reason)
        self.protocol.connectionLost(reason)
        logger.debug("Reconecting after 30s")
#        sleep(30)
        self.initSoft(self.protocol, self.deviceName, self.reactor)


    def connectionLost(self, reason=None):
        """
        Called when the serial port disconnects.

        Will call C{connectionLost} on the protocol that is handling the
        serial data.
        """
#        import pdb; pdb.set_trace()
        win32file.CancelIo(self.hComPort)
        self.reactor.removeEvent(self._overlappedRead.hEvent)
        self.reactor.removeEvent(self._overlappedWrite.hEvent)
        win32file.CloseHandle(self._overlappedRead.hEvent)
        win32file.CloseHandle(self._overlappedWrite.hEvent)
        
        abstract.FileDescriptor.connectionLost(self, reason)
        win32file.CloseHandle(self.hComPort)
        self.protocol.connectionLost(reason)
        logger.debug("Hard reconecting after 10s")
        sleep(10)
        self.initHard(self.protocol, self.deviceName, self.reactor)
        self.initSoft(self.protocol, self.deviceName, self.reactor)
