# -*-mode: python; py-indent-offset: 4; indent-tabs-mode: nil; encoding: utf-8-dos; coding: utf-8 -*-

"""
A PikaChart object is a simple abstraction to encapsulate a Mt4 chart
that has a RabbitMQ connection on it. There should be only one connection for
the whole application, so it is set as the module variable oCONNECTION.

This module can be run from the command line to test RabbitMQ with a listener
such as OTMql427/PikaListener.py. Give the message you want to publish
as arguments to this script, or --help to see the options.
"""

import sys, logging
import time
import traceback
import pika

oLOG = logging

from Mq4Chart import Mq4Chart, oFindChartByName, lFindAllCharts
from PikaListener import PikaMixin
from Mt4SafeEval import sPySafeEval
from SimpleFormat import lUnFormatMessage

if True:
    ePikaCallme = "PikaCallme disabled "
    PikaCallme = None
else:
    # The callme server is optional and may not be installed.
    # But it might be a whole lot of fun it it works.
    # It has prerequisities: kombu httplib2 amqp
    try:
        import PikaCallme
        ePikaCallme = ""
    except ImportError as e:
        ePikaCallme = "Failed to import PikaCallme: " + str(e)
        PikaCallme = None

class PikaChart(Mq4Chart, PikaMixin):

    iDeliveryMode = 1 # (non-persisted)
    sContentType = 'text/plain'

    def __init__(self, sChartId, **dParams):
        Mq4Chart.__init__(self, sChartId, **dParams)
        PikaMixin.__init__(self, sChartId, **dParams)
        self.sChartId = sChartId
        self.sQueueName = "listen-for-commands"

    def vPikaRecvOnListener(self, sQueueName, lBindingKeys):
        if self.oListenerChannel is None:
            self.eBindBlockingListener(sQueueName, lBindingKeys)
        assert self.oListenerChannel, "vPikaRecvOnListener: oListenerChannel is null"
        #FixMe: does this block?
        # http://www.rabbitmq.com/amqp-0-9-1-reference.html#basic.consume
        # no-wait no-wait
        # not in pika.channel.Channel.basic_consume
        self.oListenerChannel.basic_consume(self.vPikaCallbackOnListener,
                                            queue=self.oListenerQueueName,
                                            exclusive=True,
                                            no_ack=False
        )

    def eHeartBeat(self, iTimeout=0):
        """
        The heartbeat is usually called from the Mt4 OnTimer.
        We push a simple Print exec command onto the queue of things
        for Mt4 to do if there's nothing else happening. This way we get
        a message in the Mt4 Log,  but with a string made in Python.
        """
        if self.oQueue.empty():
            # only push if there is nothing to do
            sTopic = 'exec'
            sMark = "%15.5f" % time.time()
            sInfo = "PY: " +sMark
            sMess = "%s|%s|0|%s|Print|%s" % (sTopic, self.sChartId, sMark, sInfo,)
            self.eMq4PushQueue(sMess)

        # while we are here flush stdout so we can read the log file
        # whilst the program is running
        sys.stdout.flush()
        sys.stderr.flush()

        # now for the hard part - see if there is anything to receive
        # does this block? do we set a timeout?
        if self.oListenerChannel is None:
            # , 'json.#'
            lBindingKeys = ['cmd.#', 'eval.#']
            self.vPikaRecvOnListener(self.sQueueName, lBindingKeys)

        # This is the disabled callme server code
        if iTimeout > 0 and self.oListenerServer:
            # join it and do a little work but dont block for long
            # cant use self.oListenerServer.wait()
            print "DEBUG: listening on server"
            self.oListenerServer.drain_event(iTimeout=iTimeout)

        return ""

    def vPikaDispatchOnListener(self, sBody, oProperties=None):
        #? do we need the oProperties for content-type?
        # 'content_encoding', 'content_type', 'correlation_id', 'decode', 'delivery_mode', 'encode', 'expiration', 'headers', 'message_id', 'priority', 'reply_to', 'timestamp', 'type', 'user_id'

        lArgs = lUnFormatMessage(sBody)
        sMsgType = lArgs[0]
        sChart = lArgs[1]
        sIgnore = lArgs[2] # should be a hash on the payload
        sMark = lArgs[3]
        sVerbMaybe = lArgs[4]
        gPayload = lArgs[4:] # overwritten below

        if sMsgType == 'cmd':
            # FixMe; dispatch to the right chart
            lChartInstances = lFindAllCharts()
            if not lChartInstances:
                # this should never happen
                sys.stdout.write("ERROR: vPikaDispatchOnListener no charts\n")
                self.eMq4PushQueue(sBody)
                return
            if sChart.find('ANY') >= 0:
                #? use self?
                oElt =lChartInstances[0]
                oElt.eMq4PushQueue(sBody)
                return
            if sChart.find('ALL') >= 0:
                for oElt in lChartObjects:
                    oElt.eMq4PushQueue(sBody)
                return

            o = oFindChartByName(sChart)
            if o is not None:
                o.eMq4PushQueue(sBody)
                return

            sys.stdout.write("WARN: vPikaDispatchOnListener unrecognized sBody " +sBody +"\n")
            sys.stdout.flush()

            self.eMq4PushQueue(sBody)
            return

        #? assume eval is on any chart?
        if sMsgType == 'eval':
            # unused
            lRetval = ['retval']
            lRetval += lArgs[1:3]
            sCmd = lArgs[4]
            if len(lArgs) > 5:
                sCmd += '(' +lArgs[5] +')'
            sRetval = sPySafeEval(sCmd)
            if sRetval.find('ERROR:') >= 0:
                lRetval += ['error', sRetval]
            else:
                lRetval += ['string', sRetval]
            sRetval = '|'.join(lRetval)
            # FixMe; dispatch to the right chart
            self.eReturnOnSpeaker('retval', sRetval, sBody)
            return

    def vPikaCallbackOnListener(self, oChannel, oMethod, oProperties, sBody):
        assert sBody, "vPikaCallbackOnListener: no sBody received"
        oChannel.basic_ack(delivery_tag=oMethod.delivery_tag)
        sMess = "vPikaCallbackOnListener Listened: %r" % sBody
        sys.stdout.write("INFO: " +sMess +"\n")
        sys.stdout.flush()
        # we will assume that the sBody
        # is a "|" seperated list of command and arguments
        # FixMe: the sMess must be in the right format
        # FixMe: refactor for multiple charts:
        # we must push to the right chart
        try:
            self.vPikaDispatchOnListener(sBody, oProperties)
        except Exception as e:
            sys.stdout.write("ERROR: " +str(e) +"\n" + \
                             traceback.format_exc(10) +"\n")
            sys.stdout.flush()
            sys.exc_clear()

    # unused
    def eStartCallmeServer(self, sId='Mt4Server'):
        # The callme server is optional and may not be installed
        if not PikaCallme:
            return ePikaCallme
        if self.oListenerServer is None:
            oServer = PikaCallme.Server(server_id=sId)
            # danger - we are running this in the main thread
            # self.oListenerThread = _run_server_thread(oServer)
            oServer.connect()
            oServer.register_function(sPySafeEval, 'sPySafeEval')
            oServer.register_function(self.eMq4PushQueue, 'eMq4PushQueue')
            self.oListenerServer = oServer
            print "DEBUG: started the callme server %d" % id(oServer)

        return ""


def iMain():
    from PikaArguments import oParseOptions

    sUsage = __doc__.strip()
    oArgParser = oParseOptions(sUsage)
    oArgParser.add_argument('lArgs', action="store",
                            nargs="*",
                            help="the message to send (required)")
    oOptions = oArgParser.parse_args()
    lArgs = oOptions.lArgs

    assert lArgs, "Give the command you want to send as arguments to this script"

    sSymbol = 'ANY'
    iPeriod = 0
    sTopic = 'cmd'
    sMark = "%15.5f" % time.time()
    sMsg = "%s|%s|0|%s|%s|str|%s" % (sTopic, sSymbol+str(iPeriod), sMark, '|'.join(lArgs),)

    oChart = None
    try:
        oChart = PikaChart('oANY_0_FFFF_0', **oOptions.__dict__)
        iMax = 1
        i = 0
        print "Sending: %s %d times " % (sMsg, iMax,)
        while i < iMax:
            # send a burst of iMax copies
            oChart.eSendOnSpeaker('cmd', sMsg)
            i += 1
        # print "Waiting for message queues to flush..."
        time.sleep(1.0)
    except KeyboardInterrupt:
        pass
    except Exception as e:
        print(traceback.format_exc(10))

    try:
        if oChart:
            print "DEBUG: Waiting for message queues to flush..."
            oChart.bCloseConnectionSockets(oOptions)
            time.sleep(1.0)
    except KeyboardInterrupt:
        pass

if __name__ == '__main__':
    iMain()