import time
import imp
import sys
import inc_const as const
from inc_cfg import *

# Load configuration
cfg_file = imp.load_source("cfg_file", ARGS[1])

# Trigger address switch for media flow between ua1 and ua2.
# When the receiver uses STUN while both sides are actually in the same
# private network, initial media packets may be sent to public IP address
# as specified in the receiver SDP and those packets may not be delivered
# if the NAT does not support hairpinning. This function will make both
# sides to send some initial packets to trigger destination address switch
# in media transport, so future packets will be delivered to the correct
# address (private IP address).
def hole_punch(ua1, ua2):
    if ua1.use_telnet:
        ua1.send("# 987")
    else:
        ua1.send("#")
        ua1.expect("#")
        ua1.send("987")

    if ua2.use_telnet:
        ua2.send("# 789")
    else:
        ua2.send("#")
        ua2.expect("#")
        ua2.send("789")

    time.sleep(0.1)


# Check media flow between ua1 and ua2
def check_media(ua1, ua2):
    if ua1.use_telnet:
        ua1.send("# 1122")
    else:
        ua1.send("#")
        ua1.expect("#")
        ua1.send("1122")
    ua2.expect(const.RX_DTMF + "1")
    ua2.expect(const.RX_DTMF + "1")
    ua2.expect(const.RX_DTMF + "2")
    ua2.expect(const.RX_DTMF + "2")


# Test body function
def test_func(t):
    callee = t.process[0]
    caller = t.process[1]

    # if have_reg then wait for couple of seconds for PUBLISH
    # to complete (just in case pUBLISH is used)
    if callee.inst_param.have_reg:
        time.sleep(1)
    if caller.inst_param.have_reg:
        time.sleep(1)
        
    # Check if ICE is used
    use_ice = ("--use-ice" in caller.inst_param.arg) and ("--use-ice" in callee.inst_param.arg)

    # Check if STUN is used (by either side)
    use_stun = ("--stun-srv" in caller.inst_param.arg) or ("--stun-srv" in callee.inst_param.arg)

    # Check if DTLS-SRTP is used
    use_dtls_srtp = "--srtp-keying=1" in caller.inst_param.arg

    # Caller making call
    if caller.use_telnet:
        caller.send("call new " + t.inst_params[0].uri)
    else:
        caller.send("m")
        caller.send(t.inst_params[0].uri)
    caller.expect(const.STATE_CALLING)
    
    # Callee waits for call and answers with 180/Ringing
    time.sleep(0.2)
    callee.expect(const.EVENT_INCOMING_CALL)
    if callee.use_telnet:
        callee.send("call answer 180")
    else:
        callee.send("a")
        callee.send("180")
    callee.expect("SIP/2.0 180")
    caller.expect("SIP/2.0 180")

    # Synchronize stdout
    caller.sync_stdout()
    callee.sync_stdout()

    # Callee answers with 200/OK
    if callee.use_telnet:
        callee.send("call answer 200")
    else:
        callee.send("a")
        callee.send("200")

    # Wait until call is connected in both endpoints
    ##time.sleep(0.2)
    caller.expect(const.STATE_CONFIRMED)
    callee.expect(const.STATE_CONFIRMED)

    # Synchronize stdout
    caller.sync_stdout()
    callee.sync_stdout()
    ##time.sleep(0.1)
    caller.sync_stdout()
    callee.sync_stdout()
    
    # Wait ICE nego before checking media
    if use_ice:
        # Unfortunately ICE nego may race with STATE_CONFIRMED (esp. on callee side), so let's just sleep
        #caller.expect("ICE negotiation success")
        #callee.expect("ICE negotiation success")
        # Additional wait for ICE updating address (via UPDATE/re-INVITE)
        time.sleep(0.5)

    # Wait DTLS-SRTP nego before checking media
    if use_dtls_srtp:
        # Unfortunately DTLS-SRTP nego may race with STATE_CONFIRMED, so let's just sleep
        #caller.expect("SRTP started, keying=DTLS-SRTP")
        #callee.expect("SRTP started, keying=DTLS-SRTP")
        time.sleep(0.5)

    # Trigger address switch before checking media
    if use_stun and not use_ice:
        hole_punch(caller, callee)

    # Test that media is okay
    check_media(caller, callee)
    check_media(callee, caller)

    # Hold call by caller
    if caller.use_telnet:
        caller.send("call hold")
    else:
        caller.send("H")
    caller.expect("INVITE sip:")
    callee.expect("INVITE sip:")
    callee.expect(const.MEDIA_HOLD)
    caller.expect(const.MEDIA_HOLD)
    
    # Synchronize stdout
    caller.sync_stdout()
    callee.sync_stdout()

    # Release hold
    ##time.sleep(0.5)
    if caller.use_telnet:
        caller.send("call reinvite")
    else:
        caller.send("v")
    caller.expect("INVITE sip:")
    callee.expect("INVITE sip:")
    callee.expect(const.MEDIA_ACTIVE, title="waiting for media active after call hold")
    caller.expect(const.MEDIA_ACTIVE, title="waiting for media active after call hold")

    # Synchronize stdout
    caller.sync_stdout()
    callee.sync_stdout()

    # Trigger address switch before checking media
    if use_stun and not use_ice:
        hole_punch(caller, callee)

    # Test that media is okay
    check_media(caller, callee)
    check_media(callee, caller)

    # Synchronize stdout
    caller.sync_stdout()
    callee.sync_stdout()

    # Hold call by callee
    if callee.use_telnet:
        callee.send("call hold")
    else:
        callee.send("H")
    callee.expect("INVITE sip:")
    caller.expect("INVITE sip:")
    caller.expect(const.MEDIA_HOLD)
    callee.expect(const.MEDIA_HOLD)
    
    # Synchronize stdout
    caller.sync_stdout()
    callee.sync_stdout()

    # Release hold
    ##time.sleep(0.1)
    if callee.use_telnet:
        callee.send("call reinvite")
    else:
        callee.send("v")
    callee.expect("INVITE sip:")
    caller.expect("INVITE sip:")
    caller.expect(const.MEDIA_ACTIVE, title="waiting for media active after call hold")
    callee.expect(const.MEDIA_ACTIVE, title="waiting for media active after call hold")

    # Synchronize stdout
    caller.sync_stdout()
    callee.sync_stdout()

    # Trigger address switch before checking media
    if use_stun and not use_ice:
        hole_punch(caller, callee)

    # Test that media is okay
    # Wait for some time for ICE negotiation
    ##time.sleep(0.6)
    check_media(caller, callee)
    check_media(callee, caller)

    # Synchronize stdout
    caller.sync_stdout()
    callee.sync_stdout()

    # UPDATE (by caller)
    if caller.use_telnet:
        caller.send("call update")
    else:
        caller.send("U")
    #caller.sync_stdout()
    callee.expect(const.MEDIA_ACTIVE, title="waiting for media active with UPDATE")
    caller.expect(const.MEDIA_ACTIVE, title="waiting for media active with UPDATE")
    
    # Synchronize stdout
    caller.sync_stdout()
    callee.sync_stdout()

    # Trigger address switch before checking media
    if use_stun and not use_ice:
        hole_punch(caller, callee)

    # Test that media is okay
    ##time.sleep(0.1)
    check_media(caller, callee)
    check_media(callee, caller)

    # UPDATE (by callee)
    if callee.use_telnet:
        callee.send("call update")
    else:
        callee.send("U")
    callee.expect("UPDATE sip:")
    caller.expect("UPDATE sip:")
    caller.expect(const.MEDIA_ACTIVE, title="waiting for media active with UPDATE")
    callee.expect(const.MEDIA_ACTIVE, title="waiting for media active with UPDATE")
    
    # Synchronize stdout
    caller.sync_stdout()
    callee.sync_stdout()

    # Trigger address switch before checking media
    if use_stun and not use_ice:
        hole_punch(caller, callee)

    # Test that media is okay
    ##time.sleep(0.1)
    check_media(caller, callee)
    check_media(callee, caller)

    # Synchronize stdout
    caller.sync_stdout()
    callee.sync_stdout()

    # Set codecs in both caller and callee so that there is
    # no common codec between them.
    # In caller we only enable PCMU, in callee we only enable PCMA
    if caller.use_telnet:
        caller.send("Cp * 0")
        caller.send("Cp")
        caller.expect("PCMU/8000.* prio: 0")
        caller.send("Cp PCMU 120")
        caller.send("Cp")
        caller.expect("PCMU/8000.* prio: 120")
    else:
        caller.send("Cp")
        caller.expect("Enter codec")
        caller.send("* 0")
        caller.send("Cp")
        caller.expect("Enter codec")
        caller.send("pcmu 120")

    if callee.use_telnet:
        callee.send("Cp * 0")
        callee.send("Cp")
        callee.expect("PCMA/8000.* prio: 0")
        callee.send("Cp PCMA 120")
        callee.send("Cp")
        callee.expect("PCMA/8000.* prio: 120")
    else:
        callee.send("Cp")
        callee.expect("Enter codec")
        callee.send("* 0")
        callee.send("Cp")
        callee.expect("Enter codec")
        callee.send("pcma 120")

    # Test when UPDATE fails (by callee)
    if callee.use_telnet:
        callee.send("call update")
    else:
        callee.send("U")
    caller.expect("SIP/2.0 488")
    callee.expect("SIP/2.0 488")
    callee.sync_stdout()
    caller.sync_stdout()
    
    # Test that media is still okay
    ##time.sleep(0.1)
    check_media(caller, callee)
    check_media(callee, caller)

    # Test when UPDATE fails (by caller)
    if caller.use_telnet:
        caller.send("call update")
    else:
        caller.send("U")
    caller.expect("UPDATE sip:")
    callee.expect("UPDATE sip:")
    callee.expect("SIP/2.0 488")
    caller.expect("SIP/2.0 488")
    caller.sync_stdout()
    callee.sync_stdout()
    
    # Test that media is still okay
    ##time.sleep(0.1)
    check_media(callee, caller)
    check_media(caller, callee)

    # Hangup call
    ##time.sleep(0.1)
    if caller.use_telnet:
        caller.send("call hangup")
    else:
        caller.send("h")

    # Wait until calls are cleared in both endpoints
    caller.expect(const.STATE_DISCONNECTED)
    callee.expect(const.STATE_DISCONNECTED)
    

# Here where it all comes together
test = cfg_file.test_param
test.test_func = test_func

