#!/usr/bin/env python
'''
Copyright (C) 2010, Digium, Inc.
David Vossel <dvossel@digium.com>

This program is free software, distributed under the terms of
the GNU General Public License Version 2.
'''

import sys
import os
from twisted.internet import reactor
import pjsua as pj
import threading

sys.path.append("lib/python")
from asterisk.test_case import TestCase
import logging
from asterisk.version import AsteriskVersion

LOGGER = logging.getLogger(__name__)

class AutoAnswerCallback(pj.AccountCallback):
    def __init__(self, account):
        self.sem = threading.Semaphore(0)
        pj.AccountCallback.__init__(self, account)

    def wait(self):
        if self.sem:
            self.sem.acquire()

    def on_reg_state(self):
        if self.sem:
            if self.account.info().reg_status >= 200:
                self.sem.release()
                self.sem = None

    def on_incoming_call(self, call):
        call.answer(200)

class AttTransferTest(TestCase):
    def __init__(self):
        TestCase.__init__(self)
        #self.reactor_timeout = 60
        self.create_asterisk()
        self.chans = []
        self.final_bridge = 0
        self.lib = None
        self.ext_a = None
        self.ext_b = None
        self.ext_c = None
        self.callToB = None
        self.callToC = None

    def run(self):
        TestCase.run(self)
        self.create_ami_factory()

    def ami_connect(self, ami):
        # start pjsua clients
        self.lib = pj.Lib()
        try:
            self.lib.init()
            self.lib.create_transport(pj.TransportType.UDP, pj.TransportConfig())
            self.lib.set_null_snd_dev()
            self.lib.start()

            # we'll need this for later...
            self.ext_a = self.lib.create_account(pj.AccountConfig("127.0.0.1", "end_a"))
            self.ext_b = self.lib.create_account(pj.AccountConfig("127.0.0.1", "end_b"))
            self.ext_c = self.lib.create_account(pj.AccountConfig("127.0.0.1", "end_c"))
            # only legs B and C receive calls, so only those two need to register and autoanswer
            ext_b_cb = AutoAnswerCallback(self.ext_b)
            ext_c_cb = AutoAnswerCallback(self.ext_c)
            self.ext_b.set_callback(ext_b_cb)
            self.ext_c.set_callback(ext_c_cb)
            # wait for registration
            ext_b_cb.wait()
            ext_c_cb.wait()

        except pj.Error, e:
            LOGGER.error("Exception: " + str(e))
            self.doCleanup()
            return

        # register callbacks required to handle call completion events
        if AsteriskVersion() < AsteriskVersion('12'):
            self.ami[0].registerEvent('Bridge', self.bridge_callback)
            self.ami[0].registerEvent('VarSet', self.bridgepeer_callback)
        else:
            self.ami[0].registerEvent('BridgeEnter',
                                      self.bridge_enter_callback)
            self.ami[0].registerEvent('AttendedTransfer',
                                      self.transfer_callback)

        # kick off first call from A to B
        LOGGER.info("Kicking off A-to-B call")
        self.callToB = self.ext_a.make_call("sip:call_b@127.0.0.1:5060")

    def bridge_callback(self, ami, event):
        '''
        Pre-Asterisk 12 bridge callback.

        This callback stores the channels that end_a is connected to in
        self.chans. Once self.chans has two channels in it, then end_a
        transfers end_b to end_c.
        '''

        self.chans.append(event['channel2'])
        numchans = len(self.chans)
        if numchans == 1:
            # kick off second call from A to C
            LOGGER.info("Kicking off A-to-C call")
            self.callToC = self.ext_a.make_call("sip:call_c@127.0.0.1:5060")
        elif numchans == 2:
            # both channels are now up, so initiate the transfer
            LOGGER.info("Kicking off transfer")
            self.callToC.transfer_to_call(self.callToB)

    def bridgepeer_callback(self, ami, event):
        '''
        Pre-Asterisk 12 bridge callback.

        This callback checks that after the transfer has completed, that the
        BRIDGEPEER variable setting for the two channels is what is expected.
        end_b should have end_c as its BRIDGEPEER, and end_c should have end_b
        as its BRDIGEPEER.
        '''

        if event['variable'] != "BRIDGEPEER" or len(self.chans) < 2:
            return

        LOGGER.info("Inspecting BRIDGEPEER VarSet")

        # we should get 2 bridgepeers with swapped channel and value headers indicating the bridged channels
        if self.chans[:2] == [event['channel'], event['value']] or\
            self.chans[:2] == [event['value'], event['channel']]:
            LOGGER.info("Got expected VarSet")
            self.final_bridge += 1
            if self.final_bridge == 2:
                LOGGER.info("Transfer successful!")
                # success!
                self.passed = True
                self.doCleanup()

    def bridge_enter_callback(self, ami, event):
        '''
        Asterisk 12+ bridge enter calback.

        We expect this callback to be called a total of five times.
        1. end_a enters bridge 1
        2. end_b enters bridge 1
        3. end_a enters bridge 2
        4. end_c enters bridge 2
        5. Either end_c enters bridge 1 or end_b enters bridge 2

        After the end_a and end_b enter bridge 1, we initiate a call from end_a
        to end_c. After end_a and end_c enter bridge2, we initiate an attended
        transfer from end_a to transfer end_b to end_c.
        '''
        LOGGER.info("Channel '%s' entered bridge '%s'" % (event.get('channel'),
                    event.get('bridgeuniqueid')))
        self.chans.append(event.get('channel'))
        if (len(self.chans) == 2):
            self.callToC = self.ext_a.make_call("sip:call_c@127.0.0.1:5060")
        elif (len(self.chans) == 4):
            self.callToC.transfer_to_call(self.callToB)

    def transfer_callback(self, ami, event):
        '''
        Asterisk 12+ attended transfer callback.

        We expect this callback to be called exactly once. In this, we ensure
        that the transfer was successful.
        '''
        LOGGER.info("Got attended transfer callback")
        if event.get('result') == 'Success':
            self.passed = True
            LOGGER.info("Successful transfer")
        else:
            LOGGER.error("Unsuccessful transfer: %s" % event.get('result'))

        self.doCleanup()

    def doCleanup(self):
        #self.ami[0].hangup(self.chans[0])
        self.ast[0].cli_exec("core show locks")   # get lock output in case of deadlock before tearing down.
        self.ast[0].cli_exec("core show channels")# if channels are still up for some reason, we want to know that as well
        self.lib.destroy()
        self.lib = None
        reactor.stop()

def main():
    # Run Attended Transfer Test
    test = AttTransferTest()
    reactor.run()
    if test.passed != True:
        return 1
    return 0

if __name__ == "__main__":
    sys.exit(main() or 0)

