# -*- test-case-name: txgsm.tests.test_protocol -*-
# -*- coding: utf-8 -*-
from twisted.internet import reactor
from twisted.protocols.basic import LineReceiver
from twisted.internet.defer import Deferred
from twisted.python import log

from .utils import quote

from messaging.sms import SmsSubmit, SmsDeliver


class TxGSMProtocol(LineReceiver):

    CTRL_Z = '\x1a'
    delimiter = '\r\n'
    verbose = False

    def __init__(self):
        # AT switches being purely line oriented and sometimes not
        # especially when sending multipart SMSs which has a '^> $' prompt
        # without a '\r\n' that we need to wait for.
        # As a result using lineReceived() does not always work.
        self.setRawMode()
        self.deferreds = []
        self.buffer = b''

    def log(self, msg):
        if self.verbose:
            log.msg(msg)

    def connectionMade(self):
        self.log('Connection made')

    def send_command(self, command, expect='OK', timeout=None):
        self.log('Sending: %r' % (command,))
        resp = Deferred()
        resp.addCallback(self.debug)
        if timeout:
            reactor.callLater(timeout, resp.cancel)
        self.deferreds.append((command, expect, resp))
        self.sendLine(command)
        return resp

    def debug(self, resp):
        self.log('Received: %r' % (resp,))
        return resp

    def next(self, command, expect='OK'):
        def handler(result):
            d = self.send_command(command, expect)
            d.addCallback(lambda r: result + [r])
            return d
        return handler

    def configure_modem(self):
        # Sensible defaults shamelessly copied from pygsm.
        d = Deferred()
        d.addCallback(self.next('ATE0'))  # Disable echo
        d.addCallback(self.next('AT+CMGF=0'))  # PDU mode
        d.addCallback(self.next('AT+CMEE=1'))  # More useful errors
        d.addCallback(self.next('AT+CSMS=1'))  # set SMS mode to phase 2+
        d.callback([])
        return d

    def send_sms(self, msisdn, text):
        sms = SmsSubmit(msisdn, text)
        # NOTE: The use of the Deferred here is a bit wonky
        #       I'm using it like this because it makes adding callbacks
        #       in a for-loop easier since we're potentially sending
        #       SMSs bigger than 160 chars.
        d = Deferred()
        for pdu in sms.to_pdu():
            d.addCallback(self.next(
                'AT+CMGS=%d' % (pdu.length,),
                expect='> '))
            d.addCallback(self.next('%s%s' % (pdu.pdu, self.CTRL_Z)))

        d.callback([])
        return d

    def dial_ussd_code(self, code):
        return self.send_command('AT+CUSD=1,"%s",15' % (quote(code),),
                                 expect='+CUSD')

    def list_received_messages(self, status=4):
        d = self.send_command('AT+CMGL=%i' % (status,))

        def parse_cmgl_response(result):
            response = result['response']
            # Lines alternative between the +CMGL response and the
            # actual PDU containing the SMS
            found = False
            messages = []
            for line in response:
                if line.startswith('+CMGL:'):
                    found = True
                elif found:
                    messages.append(SmsDeliver(line))
                    found = False

            return messages

        d.addCallback(parse_cmgl_response)
        return d

    def probe(self):
        """
        See if we're talking to something GSM-like and if so,
        try and get some useful information out of it.
        """
        d = Deferred()
        d.addCallback(self.next('ATE0'))
        d.addCallback(self.next('AT+CIMI'))
        d.addCallback(self.next('AT+CGMM'))
        reactor.callLater(0, d.callback, [])
        return d

    def rawDataReceived(self, data):
        self.buffer += data

        if not self.deferreds:
            log.err('Unsollicited response: %r' % (data,))
            return

        _, expect, _ = self.deferreds[0]

        if expect in self.buffer:
            command, expect, deferred = self.deferreds.pop(0)
            return_buffer, self.buffer = self.buffer, b''
            result = {
                'command': [command],
                'expect': expect,
                'response': filter(None, return_buffer.split(self.delimiter))
            }
            deferred.callback(result)
