#!/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, hartFrameMotes, 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
        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.hartCommandsLock   = threading.Lock()
        self.apiDef             = HartMgrDefinition.HartMgrDefinition()
        self.hartDef            = hartDefinition.hartDefinition()
        self.notifClientHandler = None
        self.hartCallbackID     = 0

        # create window
        self.window = dustWindow.dustWindow('WirelessHART Manager Control',
                                 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()

        self.commandFrame    = dustFrameCommand.dustFrameCommand(
            parentElem       = self.window,
            guiLock          = self.guiLock,
            selectedCb       = self._commandFrameCb_selected,
            responseCb       = self._commandFrameCb_response,
            responseErrorCb  = self._commandFrameCb_responseError,
            row              = 1,
            column           = 0,
        )
        self.commandFrame.apiLoaded(self.apiDef)

        self.responseFrame   = dustFrameResponse.dustFrameResponse(
            parentElem       = self.window,
            guiLock          = self.guiLock,
            row              = 2,
            column           = 0,
        )
        self.responseFrame.apiLoaded(self.apiDef)

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

        self.toolTipFrame    = dustFrameText.dustFrameText(
            parentElem       = self.window,
            guiLock          = self.guiLock,
            frameName        = "tooltip",
            row              = 4,
            column           = 0,
        )

        # frame for active HART motes
        self.HARTmotesFrame = hartFrameMotes.hartFrameMotes(
            parentElem=self.window,
            guiLock=self.guiLock,
            hartDef=self.hartDef,
            frameName="HART motes",
            row=5,
            column=0
        )

        # frame for HART commands
        self.HARTcommandFrame = hartFrameCommand.hartFrameCommand(
            parentElem=self.window,
            guiLock=self.guiLock,
            motesFrame = self.HARTmotesFrame,
            selectedCb=self._HARTcommandFrameCb_selected,
            sendCb = self._HARTcommandFrameCb_send,
            hartDef=self.hartDef,
            frameName="HART command",
            row=6,
            column=0
        )

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

        # frame for HART command decsription
        self.HARTtipFrame = dustFrameText.dustFrameText(
            parentElem=self.window,
            guiLock=self.guiLock,
            frameName="HART command description",
            row=8,
            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):

        self.connector.dn_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.commandFrame.show()
        self.responseFrame.show()
        #self.notifFrame.show()
        self.toolTipFrame.show()
        self.HARTmotesFrame.show()
        self.HARTcommandFrame.show()
        self.HARTresponseFrame.show()
        self.HARTtipFrame.show()
        self.connectionFrame.hide()

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

        self.HARTcommandFrame.connectorLoaded(self.connector)
        self.notifFrame.connectorLoaded(self.connector)
        self.commandFrame.connectorLoaded(self.connector)
        self.HARTmotesFrame.connectorLoaded(self.connector)
        self.HARTmotesFrame.updateMotesList()

        # subscribe
        # change events to event-network to get only events starting with 'net'
        self.connector.dn_subscribe('data event-network')

    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

    def _commandFrameCb_selected(self,nameArray):
            try:
                output  =   []
                output +=   [
                    self.apiDef.getDescription(
                        ApiDefinition.ApiDefinition.COMMAND,
                        nameArray
                    )
                ]
                containsUtc = False
                for field in self.apiDef.getRequestFieldNames(nameArray):
                    if field.count('utc'):
                        containsUtc = True
                        break
                if containsUtc:
                    output  += ['\n\nNote: UTC fields are shown as a single struct in the API documentation.']
                self._writeTooltip(''.join(output))
            except CommandError as err:
                self._writeTooltip(str(err))

    def _commandFrameCb_response(self,commandArray,responseFields):
        print(commandArray)
        print(responseFields)
        self.responseFrame.indicateFields(
            commandArray,
            responseFields,
        )

    def _commandFrameCb_responseError(self,errorText):
        self.responseFrame.indicateError(
            errorText
        )

    def _HARTcommandFrameCb_selected(self,param):
        try:
            output = []
            output += [self.hartDef.getCommandDescription(param)]

            self._writeHARTtip(''.join(output))
        except CommandError as err:
            self._writeHARTtip(str(err))

    def _HARTcommandFrameCb_send(self):
        self.hartCallbackID = self.HARTcommandFrame.getCallbackId()

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

    def _writeTooltip(self,textToWrite):
        output  = []
        output += [textToWrite]
        output += [
            '\n\nNote: to enter a value in a field of type HEXDATA, please enter two digits for each byte (zero padded).'
        ]
        self.toolTipFrame.write(''.join(output))

    def _writeHARTtip(self,textToWrite):
        output  = []
        output += [textToWrite]
        output += [
            '\n\nNote: to enter a value in a field of type HEXDATA, please enter two digits for each byte (zero padded).'
        ]
        self.HARTtipFrame.write(''.join(output))

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

    def _getLastNotif(self):
        with self.lastNotifLock:
            returnVal      = self.lastNotif
            try:
                if returnVal[0][0] == 'data':
                    data = returnVal[1]
                    if data['isBroadcast']:
                        if data['callbackId'] == self.hartCallbackID:
                            print(data['payload'])
                            self.HARTresponseFrame.decodeHartResponse(data['payload'])
                    else:
                        self.HARTresponseFrame.decodeHartResponse(data['payload'])

                #else:
                #    print(returnVal[0])
                #    print(returnVal[0][1])
                if  returnVal[0][0] == 'event':
                    if (returnVal[0][1] == 'MoteLive') or (returnVal[0][1] == 'MoteDisconnect'):
                        print('Mote connected or disconnected')
                        self.HARTmotesFrame.updateMotesList()
            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
# \}
#
