from twisted.python import log
from twisted.internet.defer import inlineCallbacks
from twisted.internet import reactor

from vumi.message import Message
from vumi.service import Worker, Consumer, Publisher
from vumi.workers.truteq.util import (VumiSSMIFactory, SessionType,
                                      ussd_code_to_routing_key)


class TruTeqConsumer(Consumer):
    """
    This consumer creates the generic outbound USSD transport.
    Anything published to the `vumi.ussd` exchange with
    routing key ussd.truteq.* (* == single word match, # == zero or more words)
    """
    exchange_name = "vumi.ussd"
    exchange_type = "topic"
    durable = False
    queue_name = "ussd.truteq"
    routing_key = "ussd.truteq.*"

    def __init__(self, send_callback):
        self.send = send_callback

    def consume_message(self, message):
        log.msg("Consumed Message %s" % message)
        self.send(**message.payload)
        return True


class TruTeqPublisher(Publisher):
    """
    This publisher publishes all incoming USSD messages to the
    `vumi.ussd` exchange, its default routing key is `ussd.fallback`
    but every new message that comes in has the USSD code as the first message.
    The transport keeps this USSD code in an internal memory based dictionary
    and uses it as the routing key for the message being published.

    Successive messages coming from the client do not have the USSD code and
    the dictionary is checked for what routing key should be used.
    """
    exchange_name = "vumi.ussd"
    exchange_type = "topic"             # -> route based on pattern matching
    routing_key = 'ussd.fallback'       # -> overriden in publish method
    durable = False                     # -> not created at boot
    auto_delete = False                 # -> auto delete if no consumers bound
    delivery_mode = 2                   # -> do not save to disk

    def publish_message(self, message, **kwargs):
        log.msg("Publishing Message %s with extra args: %s" % (message,
                                                               kwargs))
        super(TruTeqPublisher, self).publish_message(message, **kwargs)


class USSDTransport(Worker):
    """
    The USSDTransport for TruTeq
    """

    def startWorker(self):
        log.msg("Starting the USSDTransport")

        username = self.config.pop('username')
        password = self.config.pop('password')
        host = self.config.pop("host")
        port = self.config.pop("port")

        # FIXME:    this needs to be done more intelligently, it stores which
        #           MSISDN dialed into which ussd code, problem is that it is
        #           memory and will be lost during restarts.
        self.storage = {}

        # start the USSD transport
        factory = VumiSSMIFactory(username, password)
        factory.onConnectionMade.addCallback(self.ssmi_connected)
        reactor.connectTCP(host, port, factory)

    @inlineCallbacks
    def ssmi_connected(self, client):
        log.msg("SSMI Connected, adding handlers")
        self.ssmi_client = client
        # link self.ussd_callback, self.sms_callback and self.errback as
        # callbacks for events generated by the SSMI protocol
        self.ssmi_client.set_handler(self)

        # Start the publisher
        self.publisher = yield self.start_publisher(TruTeqPublisher)
        # Start the consumer, pass along the send_ussd callback for sending
        # back consumed AMQP messages over TruTeq.
        self.consumer = yield self.start_consumer(TruTeqConsumer,
                                                  self.send_ussd)

    def ussd_callback(self, msisdn, ussd_type, phase, message):
        print "Received USSD, from: %s, message: %s" % (msisdn, message)

        # FIXME: See the note about self.storage
        # If it's a new session then store the message as the USSD code
        # use that as the routing key for publishing.
        if ussd_type == SessionType.NEW:
            # cache
            ussd_code = self.storage[msisdn] = message
            options = {
                'routing_key': ussd_code_to_routing_key('ussd.%s' % ussd_code),
            }

        # If its the end of a session or a session has timed-out then we
        # should remove the USSD code from the storage
        elif ussd_type in [SessionType.END, SessionType.TIMEOUT]:
            # clear cache
            if msisdn in self.storage:
                del self.storage[msisdn]
            options = {}

        # if it's an existing session then look up the USSD code from
        # the storage and use that as the routing key
        elif ussd_type == SessionType.EXISTING:
            # read cache
            ussd_code = self.storage.get(msisdn)
            options = {
                'routing_key': ussd_code_to_routing_key('ussd.%s' % ussd_code),
            }

        self.publisher.publish_message(Message(**{
            'msisdn': msisdn,
            'ussd_type': ussd_type,
            'phase': phase,
            'message': message,
        }), **options)

    def send_ussd(self, msisdn, ussd_type, message):
        print "Sending USSD, to: %s, message: %s" % (msisdn, message)
        self.ssmi_client.send_ussd(str(msisdn), str(message), str(ussd_type))

    def sms_callback(self, *args, **kwargs):
        print "Got SMS:", args, kwargs

    def errback(self, *args, **kwargs):
        print "Got Error: ", args, kwargs

    def stopWorker(self):
        log.msg("Stopping the USSDTransport")
