#!/usr/bin/python

#============================ adjust path =====================================

import sys
import os
if __name__ == "__main__":
    here = sys.path[0]
    sys.path.insert(0, os.path.join(here, '..', '..','libs'))
    sys.path.insert(0, os.path.join(here, '..', '..','external_libs'))

#============================ verify installation =============================

from SmartMeshSDK.utils import SmsdkInstallVerifier
(goodToGo,reason) = SmsdkInstallVerifier.verifyComponents(
    [
        SmsdkInstallVerifier.PYTHON,
        SmsdkInstallVerifier.PYSERIAL,
    ]
)
if not goodToGo:
    print "Your installation does not allow this application to run:\n"
    print reason
    raw_input("Press any button to exit")
    sys.exit(1)

#============================ imports =========================================

import threading
from   optparse                        import OptionParser

from   SmartMeshSDK.utils              import AppUtils,                   \
                                              FormatUtils
from   SmartMeshSDK.ApiDefinition      import HartMgrDefinition, ApiDefinition
from   SmartMeshSDK.ApiException  import CommandError,               \
                                         ConnectionError,            \
                                         QueueError
from   SmartMeshSDK.HartMgrConnector   import HartMgrConnector,          \
                                              NotifReader
from   dustUI                          import dustWindow,                 \
                                              dustFrameConnection,        \
                                              dustFrameNotifications, \
                                              dustFrameCommand,\
                                              dustFrameResponse,\
                                              dustFrameText,\
                                              hartFrameSensorData
from  whart                            import hartFrameCommand, hartDeviceVariable, hartDefinition, hartFrameResponse

#============================ logging =========================================

# local

import logging
class NullHandler(logging.Handler):
    def emit(self, record):
        pass
log = logging.getLogger('App')
log.setLevel(logging.ERROR)
log.addHandler(NullHandler())

# global

AppUtils.configureLogging()

#============================ defines =========================================

UPDATEPERIOD = 500 # in ms
DEFAULT_HOST = '127.0.0.1'
DEFAULT_PORT = 9900

#============================ body ============================================

##
# \addtogroup SensorDataReceiver
# \{
#

class NotifListener(threading.Thread):

    def __init__(self,connector,notifCb,disconnectedCb):

        # record variables
        self.connector       = connector
        self.notifCb         = notifCb
        self.disconnectedCb  = disconnectedCb

        # init the parent
        threading.Thread.__init__(self)
        self.name            = "NotifListener"
        self.daemon = True

    #======================== public ==========================================

    def run(self):
        keepListening = True
        # subscribe
        #try:

        #except:
         #   print('not connected, cannot subscribe')

        while keepListening:
            try:
                input = self.connector.getNotificationInternal(-1)
            except (ConnectionError,QueueError) as err:
                keepListening = False
            else:
                if input:
                    self.notifCb(input)
                else:
                    keepListening = False

        self.disconnectedCb()

class dataGui(object):

    def __init__(self):

        # local variables
        self.lastNotifLock      = threading.Lock()
        self.lastNotif          = None
        self.guiLock            = threading.Lock()
        self.apiDef             = HartMgrDefinition.HartMgrDefinition()
        self.hartDef            = hartDefinition.hartDefinition()
        self.notifClientHandler = None
        self.hartCallbackID     = 0

        # create window
        self.window = dustWindow.dustWindow('Burst Viewer',
                                 self._windowCb_close)

        # add a connection frame
        self.connectionFrame = dustFrameConnection.dustFrameConnection(
                                    self.window,
                                    self.guiLock,
                                    self._connectionFrameCb_connected,
                                    frameName="manager connection",
                                    row=0,column=0)
        self.connectionFrame.apiLoaded(self.apiDef)
        self.connectionFrame.show()

        # add notification frame
        self.notifFrame      = dustFrameNotifications.dustFrameNotifications(
            getNotifCb       = self._getLastNotif,
            parentElem       = self.window,
            guiLock          = self.guiLock,
            row              = 1,
            column           = 0,
        )
        self.notifFrame.apiLoaded(self.apiDef)

        # frame for HART responses
        self.HARTresponseFrame = hartFrameResponse.hartFrameResponse(
            self.window,
            self.guiLock,
            hartDef=self.hartDef,
            row=2,
            column=0,
        )

    #======================== public ==========================================

    def start(self, connect_params):

        '''
        This command instructs the GUI to start executing and reacting to
        user interactions. It never returns and should therefore be the last
        command called.
        '''
        try:
            self.window.mainloop()
        except SystemExit:
            sys.exit()

    #======================== private =========================================

    def _windowCb_close(self):
        try:
            self.connector.dn_unsubscribe()
        except:
            print('not connected, cannot unsubscribe')

        if self.notifClientHandler:
            self.notifClientHandler.disconnect()

    def _connectionFrameCb_connected(self,connector):
        '''
        \brief Called when the connectionFrame has connected.
        '''

        # store the connector
        self.connector = connector

        #self.notifFrame.show()
        #self.HARTresponseFrame.show()
        self.connectionFrame.hide()

        # add notification listener
        self.notifListener  = NotifListener(
            self.connector,
            self._notifRxCallback,
            self._connectionFrameCb_disconnected,
        )
        self.notifListener.start()

        self.notifFrame.connectorLoaded(self.connector)



        # get motes list
        self.macAddresses = []
        #try:
        motesList = self.connector.dn_getMotes()
        for i in motesList:
            # if this is operationl mote
            if (i[3] == 'Operational') and (i[7] == False):
                self.macAddresses.append(i[1])
        #print(self.macAddresses)
        #except:
        #    print('not connected, cannot get motes')


        self.macAddresses.sort()


        self.HARTmote = [hartDeviceVariable.hartDeviceVariable(
                self.window,
                self.guiLock,
                self._burstControl,
                macAddress=moteMac,
                row = self.macAddresses.index(moteMac)+3, # first two rows are APInotif and HARTresponse
                column = 0
            )for moteMac in self.macAddresses]

        for i in range(len(self.macAddresses)):
            self.HARTmote[i].show()

        self.infoFrame = dustFrameText.dustFrameText(
             parentElem=self.window,
             guiLock=self.guiLock,
             frameName="Info",
             row=len(self.macAddresses)+3,
             column=0,
        )

        if len(self.macAddresses) == 0:
            infoText = "No mote is Operational!!!"
        else:
            infoText = "Use this application to collect HART burst data from motes.\nEnable or disable burst mode using designated buttons. \nBlink button causes mote to flash its yellow LED. \nCurrent burst mode with bursted command number are indicated by the checkbox"
        self.infoFrame.write(infoText)
        self.infoFrame.show()

        # subscribe to data notifications
        self.connector.dn_subscribe('data')

        # get device variable properties and burst 0 settings with commandd 123
        # and dynamic variable values with command 3
        # plus squawk once CMD72
        # BROADCASTED command
        data = [0, 0, 0, 123, 5, 0, 246, 247, 248, 249, 0, 3, 0, 0, 72, 1, 2]# this is HART CMD123 + CMD3 + CMD72
        try:
            self.connector.dn_sendRequest(
                macAddr='FF-FF-FF-FF-FF-FF-FF-FF',
                domain='maintenance',
                priority='high',
                reliable=False,
                data=data)
        except:
            print('cannot send')

    def _connectionFrameCb_disconnected(self):
        '''
        \brief Called when the connectionFrame has disconnected.
        '''

        # update the GUI
        self.connectionFrame.updateGuiDisconnected()

        # delete the connector
        if self.connector:
            self.connector.disconnect()
        self.connector = None

    #======================== helpers =========================================


    def _notifRxCallback(self, notif):
        with self.lastNotifLock:
            print('notif')
            self.lastNotif = notif

    def _burstControl(self, macAddr, cmd):
        print("Button pressed", macAddr, cmd)
        if cmd=='enable':
            # issue cmd109
            data = [0, 0, 0, 109, 2, 2, 0]
        elif cmd == 'disable':
            # issue cmd109
            data = [0, 0, 0, 109, 2, 0, 0]
        elif cmd =='blink':
            # issue cmd72
            data=[0,0,0,72,1,2]
        else:
            print('Invalid command')
            return

        try:
            self.connector.dn_sendRequest(
                macAddr=macAddr,
                domain='maintenance',
                priority='high',
                reliable=True,
                data=data)
        except:
            print('cannot send')

    def _getLastNotif(self):
        supportedCmds = {1,3,33,109,123}
        with self.lastNotifLock:
            returnVal      = self.lastNotif
            try:
                if returnVal[0] == ['data']:
                    hartData = returnVal[1]['payload']
                    moteMac  = returnVal[1]['macAddr']
                    print(moteMac)

                    # check what CMD are we receiving
                    cmdNum = hartData[2] * 256 + hartData[3]

                    # only some HART commands are processed by this software
                    if cmdNum in supportedCmds:

                        # check if we are collecting data from this mote
                        if moteMac not in self.macAddresses:
                            # TODO update seld.macAddresses
                            print("unrecognized mote MAC")
                            moteIdx = 0
                        else:
                            moteIdx = self.macAddresses.index(moteMac)

                        if cmdNum == 123:
                            cmdFields = self.HARTresponseFrame.decodeHartResponse(hartData[:54])
                            config = {
                                'min': cmdFields['Min 0'],
                                'max': cmdFields['Max 0'],
                                'units': cmdFields['Unit 0'],
                                'classification': cmdFields['Class 0'],
                            }
                            self.HARTmote[moteIdx].config(config, cmdFields['Slot 0'] - 246)
                            config = {
                                'min': cmdFields['Min 1'],
                                'max': cmdFields['Max 1'],
                                'units': cmdFields['Unit 1'],
                                'classification': cmdFields['Class 1'],
                            }
                            self.HARTmote[moteIdx].config(config, cmdFields['Slot 1'] - 246)
                            config = {
                                'min': cmdFields['Min 2'],
                                'max': cmdFields['Max 2'],
                                'units': cmdFields['Unit 2'],
                                'classification': cmdFields['Class 2'],
                            }
                            self.HARTmote[moteIdx].config(config, cmdFields['Slot 2'] - 246)
                            config = {
                                'min': cmdFields['Min 3'],
                                'max': cmdFields['Max 3'],
                                'units': cmdFields['Unit 3'],
                                'classification': cmdFields['Class 3'],
                            }
                            self.HARTmote[moteIdx].config(config, cmdFields['Slot 3'] - 246)

                            if cmdFields['Burst Mode'] == 0:
                                self.HARTmote[moteIdx].updateBurst(False, cmdFields['Burst Command'])
                            else:
                                self.HARTmote[moteIdx].updateBurst(True, cmdFields['Burst Command'])

                            #Aggregated CMD3
                            cmdFields = self.HARTresponseFrame.decodeHartResponse(hartData[52:82])
                            values = [cmdFields['Primary Var'], cmdFields['Secondary Var'],cmdFields['Tertiary Var'],cmdFields['Quaternary Var']]
                            self.HARTmote[moteIdx].updateValues(values)

                        # cmd 1 Read primary variable
                        if cmdNum == 1:
                            cmdFields = self.HARTresponseFrame.decodeHartResponse(hartData)
                            value=[cmdFields['Primary Variable']]
                            self.HARTmote[moteIdx].updateValues(value)

                        #cmd 3 read dynamic vars and loop current
                        if cmdNum == 3:
                            cmdFields = self.HARTresponseFrame.decodeHartResponse(hartData)
                            values = [cmdFields['Primary Var'], cmdFields['Secondary Var'], cmdFields['Tertiary Var'],cmdFields['Quaternary Var']]
                            self.HARTmote[moteIdx].updateValues(values)

                        #cmd 33 - Dev vars values
                        if cmdNum == 33:
                            cmdFields = self.HARTresponseFrame.decodeHartResponse(hartData)
                            values = [cmdFields['Value 0'], cmdFields['Value 1'], cmdFields['Value 2'], cmdFields['Value 3']]
                            self.HARTmote[moteIdx].updateValues(values)

                        #cmd 109 - Configure burst mode
                        if cmdNum == 109:
                            cmdFields = self.HARTresponseFrame.decodeHartResponse(hartData)
                            if cmdFields['Burst Control Code'] == 0:
                                self.HARTmote[moteIdx].updateBurst(False,0)
                            else:
                                self.HARTmote[moteIdx].updateBurst(True,0)
                else:
                    print('Unsupported HART command received')
            except:
                pass

            self.lastNotif = None
            return returnVal


#============================ main ============================================

def main(connect_params):
    dataGuiHandler = dataGui()
    dataGuiHandler.start(connect_params)

if __name__ == '__main__':
    # Parse the command line
    parser = OptionParser("usage: %prog [options]", version="%prog 1.0")
    parser.add_option("--host", dest="host",
                      default=DEFAULT_HOST,
                      help="Mux host to connect to")
    parser.add_option("-p", "--port", dest="port",
                      default=DEFAULT_PORT,
                      help="Mux port to connect to")
    (options, args) = parser.parse_args()

    connect_params = {
        'host': options.host,
        'port': int(options.port),
    }
    main(connect_params)

##
# end of SensorDataReceiver
# \}
#
