[globals]
; local SIP UA Channels (PJSIP/50X-*) using the IMS endpoint are put in this group.
; This group is used as a semaphore/mutex by using COUNT_GROUP() on it.
IMS_GROUP=ims_group
; Stores channel name of local SIP UA (PJSIP/50X-*) using the IMS service (in a call)
; This is used to figure out which local SIP UA can create extra calls due to having its other IMS calls on HOLD.
IMS_PEER=none

[get-valid-endpoints-500]
; usage: no arguments, returns DIALGROUP object of registered and valid 050x endpoints
exten => s,1,Verbose(5, Entering get-valid-endpoints gosub.)
 same => n,Set(ENDPOINT_1_STATE=${DEVICE_STATE(PJSIP/0501)})
 same => n,Set(ENDPOINT_2_STATE=${DEVICE_STATE(PJSIP/0502)})
 same => n,Set(ENDPOINT_3_STATE=${DEVICE_STATE(PJSIP/0503)})
 same => n,Set(ENDPOINT_4_STATE=${DEVICE_STATE(PJSIP/0504)})
 same => n,Set(ENDPOINT_1_EXPR=$[$["${ENDPOINT_1_STATE}" = "UNAVAILABLE"] | $["${ENDPOINT_1_STATE}" = "UNKNOWN"] | $["${ENDPOINT_1_STATE}" = "INVALID"]])
 same => n,Set(ENDPOINT_2_EXPR=$[$["${ENDPOINT_2_STATE}" = "UNAVAILABLE"] | $["${ENDPOINT_2_STATE}" = "UNKNOWN"] | $["${ENDPOINT_2_STATE}" = "INVALID"]])
 same => n,Set(ENDPOINT_3_EXPR=$[$["${ENDPOINT_3_STATE}" = "UNAVAILABLE"] | $["${ENDPOINT_3_STATE}" = "UNKNOWN"] | $["${ENDPOINT_3_STATE}" = "INVALID"]])
 same => n,Set(ENDPOINT_4_EXPR=$[$["${ENDPOINT_4_STATE}" = "UNAVAILABLE"] | $["${ENDPOINT_4_STATE}" = "UNKNOWN"] | $["${ENDPOINT_4_STATE}" = "INVALID"]])
 same => n,Set(DIALGROUP(CALL_VALID_LIST)=) ; clear list
 same => n,ExecIf($[${ENDPOINT_1_EXPR} = 0]?Set(DIALGROUP(CALL_VALID_LIST,add)=PJSIP/0501))
 same => n,ExecIf($[${ENDPOINT_2_EXPR} = 0]?Set(DIALGROUP(CALL_VALID_LIST,add)=PJSIP/0502))
 same => n,ExecIf($[${ENDPOINT_3_EXPR} = 0]?Set(DIALGROUP(CALL_VALID_LIST,add)=PJSIP/0503))
 same => n,ExecIf($[${ENDPOINT_4_EXPR} = 0]?Set(DIALGROUP(CALL_VALID_LIST,add)=PJSIP/0504))
 same => n,Return(${DIALGROUP(CALL_VALID_LIST)})


[from-phone]

; Local SIP UA calls 500, broadcast to all other available local SIP UAs:
exten => 0500,1,Verbose(5,${EXTEN}: Call all registered pjsips from ${CALLERID(num)})
 same => n,Gosub(get-valid-endpoints-500,s,1())
 same => n,Set(DIALGROUP(CALL_EVERYONE_LIST)=${GOSUB_RETVAL})
 same => n,Set(DIALGROUP(CALL_EVERYONE_LIST,del)=PJSIP/${CALLERID(num)})  ; remove the caller
 same => n,Dial(${DIALGROUP(CALL_EVERYONE_LIST)})
 same => n,Hangup(16)

; Local SIP UA calls 50X, call the target local SIP UA:
exten => _050X,1,Verbose(5,${EXTEN}: Call pjsip endpoint from ${CALLERID(num)})
 same => n,Dial(PJSIP/${EXTEN})
 same => n,Hangup(16)

; MO Call SIP UA -> IMS:
exten => _X.!,1,Verbose(5,${EXTEN}: Call external number from ${CALLERID(num)}, IMS DEVICE_STATE=${DEVICE_STATE(PJSIP/volte_ims)}, IMS_PEER=${GLOBAL(IMS_PEER)})
 ; Allow only 1 MO call towards IMS, or extra ones if the same SIP UA has put previous call(s) on HOLD:
 same => n,Set(GROUP()=${GLOBAL(IMS_GROUP)})
 same => n,Set(ALLOW_EXTRA_CALL=$[ $["${DEVICE_STATE(PJSIP/volte_ims)}" == "ONHOLD"] & $["${GLOBAL(IMS_PEER)}" == "PJSIP/${CALLERID(num)}"] ])
 same => n,GotoIf($[ $[ ${GROUP_COUNT(${GLOBAL(IMS_GROUP)})} > 1] & !${ALLOW_EXTRA_CALL} ]?999)
 same => n,Set(GLOBAL(IMS_PEER)=PJSIP/${CALLERID(num)})
 same => n,Dial(PJSIP/${EXTEN}@volte_ims)
 ; Channel is removed from GROUP() automatically when it is destroyed after the call finishes.
 ; It's fine leaving IMS_PEER set since anyway it's only checked in the case where there's a call in place (GROUP_COUNT()>1),
 ; so it will be set properly whenever an initial call enters the exclusion zone guarded by GROUP_COUNT().
 same => n,Hangup(16)

 ; Reject path:
 same => 999,Verbose(1,${EXTEN}: VoLTE client already busy (${GROUP_COUNT(${GLOBAL(IMS_GROUP)})}, ${GLOBAL(IMS_PEER)}) rejecting call from SIP UA ${CALLERID(num)})
 same => n,Set(DIALSTATUS=CHANUNAVAIL)


[get-valid-endpoints-from-volte-ims]
; usage: no arguments, returns DIALGROUP object of registered and valid 050x endpoints
exten => s,1,Verbose(5, Entering get-valid-endpoints-500 gosub.)
 same => n,Set(ENDPOINT_1_STATE=${DEVICE_STATE(PJSIP/0501)})
 same => n,Set(ENDPOINT_2_STATE=${DEVICE_STATE(PJSIP/0502)})
 same => n,Set(ENDPOINT_3_STATE=${DEVICE_STATE(PJSIP/0503)})
 same => n,Set(ENDPOINT_4_STATE=${DEVICE_STATE(PJSIP/0504)})
 same => n,Set(ENDPOINT_1_EXPR=$[$["${ENDPOINT_1_STATE}" = "NOT_INUSE"] | $["${ENDPOINT_1_STATE}" = "ONHOLD"]])
 same => n,Set(ENDPOINT_2_EXPR=$[$["${ENDPOINT_2_STATE}" = "NOT_INUSE"] | $["${ENDPOINT_2_STATE}" = "ONHOLD"]])
 same => n,Set(ENDPOINT_3_EXPR=$[$["${ENDPOINT_3_STATE}" = "NOT_INUSE"] | $["${ENDPOINT_3_STATE}" = "ONHOLD"]])
 same => n,Set(ENDPOINT_4_EXPR=$[$["${ENDPOINT_4_STATE}" = "NOT_INUSE"] | $["${ENDPOINT_4_STATE}" = "ONHOLD"]])
 same => n,Set(DIALGROUP(CALL_VALID_LIST)=) ; clear list
 same => n,ExecIf($[${ENDPOINT_1_EXPR} != 0]?Set(DIALGROUP(CALL_VALID_LIST,add)=PJSIP/0501))
 same => n,ExecIf($[${ENDPOINT_2_EXPR} != 0]?Set(DIALGROUP(CALL_VALID_LIST,add)=PJSIP/0502))
 same => n,ExecIf($[${ENDPOINT_3_EXPR} != 0]?Set(DIALGROUP(CALL_VALID_LIST,add)=PJSIP/0503))
 same => n,ExecIf($[${ENDPOINT_4_EXPR} != 0]?Set(DIALGROUP(CALL_VALID_LIST,add)=PJSIP/0504))
 same => n,Return(${DIALGROUP(CALL_VALID_LIST)})

[volte-ims-call-established]
; no arguments, SUB to set global variable IMS_PEER to the Channel name of the SIP UA (501-504) using the IMS endpoint
; This SUB (Dial(U())) runs under the Channel context of the SIP UA who answered the MT call.
exten => s,1,Verbose(5, PJSIP/${CALLERID(num)}: Entering volte-ims-call-established gosub.)
 same => n,Set(GLOBAL(IMS_PEER)=PJSIP/${CALLERID(num)})
 same => n,Return()

[volte_ims]

; MT Call IMS -> SIP UA:
exten => _X.!,1,Verbose(5,${EXTEN}: Call internal number from ${CALLERID(num)}, IMS DEVICE_STATE=${DEVICE_STATE(PJSIP/volte_ims)}, IMS_PEER=${GLOBAL(IMS_PEER)})
 ; If IMS endpoint is already in use, reject it (999):
 same => n,Set(GROUP()=${GLOBAL(IMS_GROUP)})
 same => n,GotoIf($[${GROUP_COUNT(${GLOBAL(IMS_GROUP)})} > 1]?999)
 ; Figure out SIP UAs to ring:
 same => n,Gosub(get-valid-endpoints-from-volte-ims,s,1())
 same => n,Set(DIALGROUP(CALL_EVERYONE_LIST)=${GOSUB_RETVAL})
 ; Process call establishment:
 same => n,WaitForPrecondition(10,2000)
 ; OUTBOUND_GROUP var tells Dial() app to set the GROUP() of the newly created channel (501-504) instead of the calling channel (volte_ims).
 ; This way IMS_GROUP always contains channels PJSIP/50X:
 same => n,Set(OUTBOUND_GROUP=${GLOBAL(IMS_GROUP)})
 ; Once a SIP UA answers the call, volte-ims-call-established takes care of updating IMS_PEER with the new PJSIP/50X channel name:
 same => n,Dial(${DIALGROUP(CALL_EVERYONE_LIST)},,U(volte-ims-call-established))
 ; Channel is removed from GROUP() automatically when it is destroyed after the call finishes.
 same => n,Hangup(16)

 ; Reject path:
 same => 999,Verbose(1,${EXTEN}: VoLTE client already busy (${GROUP_COUNT(${GLOBAL(IMS_GROUP)})}, ${GLOBAL(IMS_PEER)}) rejecting call from IMS ${CALLERID(num)})
 same => n,Set(DIALSTATUS=BUSY)
