#!/usr/bin/env python3
# coding=utf-8
""" WiBotic Wrapper Class"""

__copyright__ = "Copyright 2018 WiBotic Inc."
__version__ = "0.1"
__email__ = "info@wibotic.com"
__status__ = "Technology Preview"

import asyncio
import websockets
import packettools
import datetime


class WiboticWrapper:

    def __init__(self, websocket_url, commandTimeout=5.0, adcTimeout=2.0):
        self._websocketUrl = websocket_url
        self._updateRequestEvent = asyncio.Event()
        self._readRequestEvent = asyncio.Event()
        self._websocket = None
        self._commandTimeout = commandTimeout
        self._adcTimeout = adcTimeout
        self.lastTxAdcUpdate = None
        self.lastRxAdcUpdate = None
        self.lastTxAdcUpdateTime = None
        self.lastRxAdcUpdateTime = None
        self._consumer_task = None


    # support the with/as syntax
    async def __aenter__(self):
        self._conn = websockets.connect(self._websocketUrl, subprotocols=['wibotic'])
        self._websocket = await self._conn.__aenter__()
        self._consumer_task = asyncio.ensure_future(self._consumer_handler())
        return self
    async def __aexit__(self, *args, **kwargs):
        await self._conn.__aexit__(*args, **kwargs)

    # support using without the with/as syntax
    async def connect(self):
        # make sure we haven't already connected
        if self._websocket is None:
            self._conn = websockets.connect(self._websocketUrl, subprotocols=['wibotic'])
            self._websocket = await self._conn.__aenter__()
            self._consumer_task = asyncio.ensure_future(self._consumer_handler())
        return self
      
    # support using without the with/as syntax
    async def disconnect(self):
        if self._consumer_task is not None:
            self._consumer_task.cancel()

        # make sure we haven't already connected
        if self._websocket is None:
            await self._websocket.close()
        return self
                
    async def _consumer_handler(self):
        while True:
            try:
                message = await self._websocket.recv()
            except:
                print('Caught recv exception')
                break

            recvdata = packettools.process_data(message)

            # is this the periodic ADC data?
            if type(recvdata) is packettools.ADCUpdate:
                if recvdata.device == packettools.DeviceID.TX:
                    self.lastTxAdcUpdate = recvdata
                    self.lastTxAdcUpdateTime = datetime.datetime.now()
                elif recvdata.device == packettools.DeviceID.RX_1:
                    self.lastRxAdcUpdate = recvdata
                    self.lastRxAdcUpdateTime = datetime.datetime.now()

            # is this a response for an updateParameter call?
            if type(recvdata) is packettools.ParamResponse:
                # check to see if the response is what we asked for
                if recvdata.device == self._updateRequestTarget and recvdata.param == self._updateRequestParam:
                    self._updateResponse = recvdata
                    self._updateRequestEvent.set()

            # is this a response for a readParameter call?
            elif type(recvdata) is packettools.ParamUpdate:
                if recvdata.device == self._readRequestTarget and recvdata.param == self._readRequestParam:
                    self._readResponse = recvdata
                    self._readRequestEvent.set()


    # function to provide a more synchronous way of updating a parameter
    async def updateParameter(self, target, param, value):

        # store this request so we can look for the response in the consumer task
        self._updateRequestTarget = target
        self._updateRequestParam = param
        self._updateRequestValue = value
        self._updateResponse = None

        # formulate the request using Wibotic packettools
        request = packettools.ParamWriteRequest(
            target,
            param,
            value
        );

        # send the parameter update request over the websocket
        await self._websocket.send(bytes(request.as_packet()))

        # wait for the consumer thread to signal that a matching response came from the Transmitter
        try:
            await asyncio.wait_for(self._updateRequestEvent.wait(), self._commandTimeout)
        except:
            return None

        # clear the event and send back the response
        self._updateRequestEvent.clear()
        response = self._updateResponse  
        self._updateResponse = None
        return response

    # function to provide a more synchronous way of updating a parameter
    async def readParameter(self, target, param):

        # store this request so we can look for the response in the consumer task
        self._readRequestTarget = target
        self._readRequestParam = param
        self._readResponse = None

        # formulate the request using Wibotic packettools
        request = packettools.ParamReadRequest(
            target,
            param
        );

        # send the parameter read request over the websocket
        await self._websocket.send(bytes(request.as_packet()))

        # wait for the consumer thread to signal that a matching response came from the Transmitter
        try:
            await asyncio.wait_for(self._readRequestEvent.wait(), self._commandTimeout)
        except:
            return None

        # clear the event and send back the response
        self._readRequestEvent.clear()
        response = self._readResponse  
        self._readResponse = None
        return response

    def _deviceConnected(self, packetData, packetTime):
        if packetData is not None:
            # information recent?
            delta = datetime.datetime.now() - packetTime
            deltaseconds = delta.total_seconds()
            if deltaseconds < self._adcTimeout:
                return True
        # OK, not connected then. 
        return False            

    # helper function to quickly find out if a receiver is connected
    def receiverConnected(self):
        if self.lastRxAdcUpdateTime is None:
            return False
        return self._deviceConnected(self.lastRxAdcUpdate, self.lastRxAdcUpdateTime)
 
    # helper function to quickly find out if a transmitter is connected
    def transmitterConnected(self):
        return self._deviceConnected(self.lastTxAdcUpdate, self.lastTxAdcUpdateTime)

 

