/*
 * jQuery phone Plugin
 * version: 0.1 (11/11/2012)
 * author:  Allan Wrethman (http://www.atmail.com)
 * @requires jQuery v1.4.2 or later
 * Copyright: Atmail
 *
 */

//N.B. naming convention is:
//	glueNotify= events notified from rtmp server or flash connector via the flash connector
//	UIcallback is an overloadable method intended to be overloaded on the ui layer to handle ui specific stuff when an event occurs
//

// phone extension for jQuery
(function($) {


	Phone = function(params)
	{

		_this = this
		//310x137 - minimum stage size to be able to show the flash auth panes
		this.callStates = {
			"STATE_IDLE":"STATE_IDLE",
			"STATE_RING":"STATE_RING",
			"STATE_TALK":"STATE_TALK",
			"STATE_FINISH":"STATE_FINISH",
			"STATE_BUSY":"STATE_BUSY",
			"STATE_HOLD":"STATE_HOLD"
		}
		this.errorCodes = {
			"AUTHENTICATION_FAIL":"AUTHENTICATION_FAIL",
			"USER_NOT_AVAILABLE":"USER_NOT_AVAILABLE",
			"TOO_MANY_REGISTER_ATTEMPTS":"TOO_MANY_REGISTER_ATTEMPTS",
			"LICENSE_RESTRICTION":"LICENSE_RESTRICTION",
			"INTERNAL_SIP_ERROR":"INTERNAL_SIP_ERROR",
			"CONNECTION_ERROR":"CONNECTION_ERROR",
			"REGISTER_EXPIRE":"REGISTER_EXPIRE"
		}
		this.errorHashEN = {
			"CONNECTION_ERROR" : "Connection fail",
			"AUTHENTICATION_FAIL" : "Register fail",
			"USER_NOT_AVAILABLE" : "Callee not found!",
			"TOO_MANY_REGISTER_ATTEMPTS" : "Connection error",
			"LICENSE_RESTRICTION" : "License restriction",
			"INTERNAL_SIP_ERROR" : "Unknown error",
			"REGISTER_EXPIRE" : "Check SIP account settings",
			"SIP_PORTS_BUSY" : "All sip ports is busy",
			"MEDIA_PORTS_BUSY" : "All media ports is busy",
			"WRONG_SIPPROVIDER_ADDRESS" : "Wrong sip provider address"
		}

		this.registrationState = 'RTMPdisconnected'
		//TODO the next 4 fields are dependant on each other and are therefore redundant, so replace with methods that all draw off the one field so no change of being out of sync
		this.currentCalls = []//these calls arent necessarily answered, but may be outbound, inbound, held, or currently talking
		this.defaultParams = {
			flashConnectorHandle : null,
			authToken : '',
			userId : '',
			phoneNumber : '',
			password : '',
			authorizationName : '',
			proxy : "",
			domain : "",
			port : "5060",
			displayName : '',
			micVolume : 50,
			speakerVolume : 90,
			SIPServerType : 'SIP', 
			debug : false,
            cssStyleTheme : 'fresh'

		}
		//overite default params with specified params
		this.params = $.extend(this.defaultParams, params)

		//default displayName to same of not specified
		if( this.params.userId !== undefined && this.params.displayName == undefined )
			this.params.displayName = this.params.userId

		if( this.params.userId !== undefined && this.params.displayName == undefined )
			this.params.authorizationName = this.params.userId

		//this.log( this.registrationState )
		this.timerHandle = null
		this.flashAuthAlreadyPoped = false
		//now wait for flash object to initialize and trigger a callback
	}



/******************************************
Main methods that UI can/should call
*******************************************/

	//Outbound call events

	//Invite remote party to call
	Phone.prototype.call = function(number)
	{

		//if mic disabled, expand flash setting panel ready to show settings pane
		if( this.params.flashConnectorHandle.isMuted() == 1 )
			this.showSettingsUICallback();
		if( number.indexOf('011') === 0 && number.indexOf('0111') !== 0 )
			alert("Sorry, this demo only allows calls to regular US phone numbers.")
		result = this.params.flashConnectorHandle.call(number,this.params.displayName || '');
		if (result == 0)
		{

			var resultEnglish = 'outboundDial'

		}
		else
		{

			var resultEnglish = "Called number is wrong."

		}
		var resultCode = result
		//this.params.flashConnectorHandle.setMicVolume(this.params.micVolume)
		this.outboundCallingUICallback(number, resultCode, resultEnglish)

	}
	Phone.prototype.outboundCallingUICallback = function( called, resultCode, resultEnglish ) { this.log('this event can be overloaded in the UI: outboundCallingUICallback('+called+', '+resultCode+','+resultEnglish+')') }

	//accept an incoming call and wait for channel activation callback
	Phone.prototype.answer = function(callId)
	{

		this.log(this.currentCalls)
		if( callId == undefined )
		{
			var call = this.getCurrentlyRingingInboundCall()
			//this.log(currentlyRingingInboundCall)
			var callId = call.id
		}
		//this.log('Answering call: ' + callId)
		this.params.flashConnectorHandle.answer(callId);
		//this.params.flashConnectorHandle.setMicVolume(this.params.micVolume)
		this.inboundAnsweredUICallback(call)

	}
	Phone.prototype.inboundAnsweredUICallback = function(call) { this.log('this event must be overloaded in the UI: inboundAnsweredUICallback(call):'); this.log(call) }

	Phone.prototype.sendDTMF = function( num )
	{

		this.log('find talking call and send send dtmf ' + num)
		//this.params.flashConnectorHandle.callProperty('call', 'sendDTMF', number);
		//result = flashphoner.sendDTMF(number); //TODO update
		var currentlyTalkingCall = this.getCurrentlyTalkingCall()
		this.log("Currently talking call id: " + currentlyTalkingCall.id)
		var currentlyTalkingCall2 = this.params.flashConnectorHandle.getCurrentCall()
		this.log("Currently talking call2 id: " + currentlyTalkingCall2.id)
		if( currentlyTalkingCall2 = undefined || currentlyTalkingCall2 == null)
		{

			this.log("Error trying to send DTMF: no talking calls")

		}
		else
		{
			//this.log(this.params)
//			this.params.flashConnectorHandle.sendDTMF("30e42d784fd91adc61e78fc95439c354@203.158.29.197","#")
			this.log("Sent DTMF tone: " + num )

		}

	}

	Phone.prototype.cancel = function()
	{

		//handle a phone cancel command (what happens is dependant on the call state)
		//TODO: handle a phone cancel command (what happens is dependant on the call state)
		this.canceledUICallback()

	}
	Phone.prototype.canceledUICallback = function() { this.log('this event must be overloaded in the UI: canceledUICallback()') }

	Phone.prototype.hold = function(callId)
	{

		if( callId == undefined )
		{

			var call = this.getCurrentCall() //gets currently talking call

		}
		else
		{

			var call = this.getCallFromId(callId)
			if( call == undefined || call == null)
			{

				this.log("Phone::hold invalid CallId")
				return

			}

		}
		this.log(call)
		$phone.params.flashConnectorHandle.setStatusHold(call.id,true)
		call.localHold = true //mark a local hold flag so that when notify tells us a call went off hold, we know if it was ourselves taking the call off hold, or it was removte taking the call off hold
		//let the notify update on real result (not blind)
		//this.localHeldUICallback(call)

	}
	//Phone.prototype.localHeldUICallback = function(call) { this.log('localHeldUICallback(call) called. This event must be overloaded in the UI.'); this.log(call) }

	Phone.prototype.retrieve = function(callId)
	{

		if( callId == undefined )
		{

			var call = this.getCurrentCall()
			//TODO: get first on hold(locally) call
		}
		else
		{

			var call = this.getCallFromId(callId)
			if( call == undefined || call == null)
			{

				this.log("Phone::retrieve invalid CallId")
				return

			}

		}
		this.log(call)
		$phone.params.flashConnectorHandle.setStatusHold(call.id,false)
		call.localHold = undefined
		delete call.localHold

		this.localRetrieveUICallback(call)

	}

	Phone.prototype.transfer = function( callee )
	{

		call = this.getCurrentCall()
		if( call != undefined && call != null && call.id != undefined && call.id != null)
			$phone.params.flashConnectorHandle.transfer(call.id, callee)

	}


	Phone.prototype.hangup = function( callee )
	{

		this.hangupAll()
		return
		//TODO: impliment this once moving to handling multiple calls

		//hangup in current context (normally hangup currently talking/!held call.)
		//N.B. come call states (like pendingCallOutbound will not have a getCurrentCall available)
		if( callee != undefined )
		{

			id = this.getCallIdFromCallee(callee)
			if( id != null )
			{

				$phone.params.flashConnectorHandle.hangup(currentCall.id)

			}

		}
		else
		{

			var currentCall = this.getCurrentlyRingingOutboundCall()
			if( currentCall == null )
			{

				this.log('Unable to hangup call: callee not passed, and no current call available')

			}
			else if( currentCall.id != undefined )
			{

				$phone.params.flashConnectorHandle.hangup(currentCall.id)

			}

		}

	}

	Phone.prototype.hangupAll = function()
	{

		this.log('hangupAll() called')
		_this = this
		$.each(this.currentCalls, function(id, call)
		{

			//_this.log('Hanging up call:')
			//_this.log(id)
			//_this.log(call)
			call.localHangup = true
			_this.params.flashConnectorHandle.hangup(call.id)

		})

	}

	Phone.prototype.busy = function(call)
	{

		this.hangupAll();//(call.id)

	}

	Phone.prototype.ignore = function(call)
	{

		this.hangupAll();//(call.id)

	}

	Phone.prototype.muteUnmute = function(mute)
	{

		if( mute )
		{

			this.params.flashConnectorHandle.setMicVolume(0)

		}
		else
		{

			this.params.flashConnectorHandle.setMicVolume(this.params.micVolume)

		}

	}

	Phone.prototype.setMicVolume = function(i)
	{

		this.params.flashConnectorHandle.setMicVolume(i)

	}

	Phone.prototype.getMicVolume = function()
	{

		return this.params.flashConnectorHandle.getMicVolume()

	}

	Phone.prototype.setSpeakerVolume = function(i)
	{

		this.params.flashConnectorHandle.setVolume(i)

	}

	Phone.prototype.getSpeakerVolume = function()
	{

		return this.params.flashConnectorHandle.getVolume()

	}


	Phone.prototype.glueNotifyAddCall = function(call) //simple adds call to array, nofity method handles what to actually do
	{

		//this.log('glueNotifyAddCall called: (call)'); this.log(call)
		//this.log("glueNotifyAddCall caller:" + call.visibleNameCaller + ", state:" + call.state + ", sip_state:" + call.sip_state + ", iHolded:" + call.iHolded + ", incoming:" + call.incoming)
		this.addCallToCurrentCalls(call)

	}

	Phone.prototype.glueNotifyRemoveCall = function(call) //simple removes call from array, nofity method handles what to actually do
	{

		//this.log('glueNotifyRemoveCall called (call):'); this.log(call)
		//this.log("glueNotifyRemoveCall caller:" + call.visibleNameCaller + ", state:" + call.state + ", sip_state:" + call.sip_state + ", iHolded:" + call.iHolded + ", incoming:" + call.incoming)
		//delete this.currentCalls[call.id]
		this.deleteCallById(call.id)


	}

	Phone.prototype.glueNotify = function(call, sipObject)
	{

		//transition state of the phone from current state to the next depending on the combnation of flags that change and then notify UI with appropriate callback
		//compare new call notification against currently called state for the call, and if it has changed, you know what has changed, g.e. a held call going from held to finished (remote hung up)

		this.log("glueNotify called")
		this.log(call)
		this.log(sipObject)

		var pCall = this.getCallFromId(call.id)
		this.log("Prev call: incoming:" + pCall.incoming + ", state:" + pCall.state + ", sip_state:" + pCall.sip_state + ", iHolded:" + pCall.iHolded);
		this.log("New  call: incoming:" + call.incoming + ", state:" + call.state + ", sip_state:" + call.sip_state + ", iHolded:" + call.iHolded )

		if( typeof(this.glueNotifyCallback) != 'undefined')
		{
			this.glueNotifyCallback(call, pCall);
		}

		if( call.isMSRP )																													//ignore other notifies for MSRP calls
		{
			pCall = call
			return
		}

		//if already on talking call and get RING then hang it up (busy) and return with no UI update
		if( call.state == "RING" || call.state == "RINGING")
		{

			if( this.currentCalls.length > 0 && this.currentCalls[0].id != call.id )
			{

				call.localHangup = true
				//this.params.flashConnectorHandle.hangup(call.id)
				//this.log( 'jquery.flashphoner.js #399: Hung up new incoming call (busy):' )
				this.log( 'jquery.flashphoner.js #430: New incoming call while on call, not doing anything:' )
				//this.log(call)
				return

			}

		}
		//this.log( 'jquery.flashphoner.js #434' )

		//call.state:RING, call.incoming:true - new incoming call
		if( call.incoming && call.state == "RING" ) //inbound ringing
		{

			if( this.params.flashConnectorHandle.isMuted() == 1 )
				this.showSettingsUICallback();

			this.log('glueNotify matched new inbound call')
			//TODO: handle if this was a second outbound.
			this.inboundRingingUICallback(call)

		}
		else if( call.state == "RING" )																										//outbound call ringing
		{

			this.log('glueNotify matched outbound call ringing')
			/*
			transitioning a call to ringing
			Prev state:RINGING, sip_state:null, iHolded:false, incoming:false
			New state:RING, sip_state:180, iHolded:false, incoming:false
			*/
			this.outboundRingingUICallback(call)

		}
		else if( call.incoming && (pCall.state == "RING" || pCall.state == "RINGING") && call.state == "TALK" )
		{

			/*
			transition from ring to talk = answered
			Prev call: state:RING, sip_state:180, iHolded:false, incoming:false
			New  call: state:TALK, sip_state:200, iHolded:false, incoming:false
			*/
			this.log('glueNotify matched inbound call transitioning from RING to TALK (inbound answered by local) - nothing more to do we already did stuff on clicking answer')
			//already called by .answer()
			//this.inboundAnsweredUICallback(call)

		}
		else if( !call.incoming && (pCall.state == "RING" || pCall.state == "RINGING") && call.state == "TALK" )
		{

			/*
			transition from ring to talk = answered
			Prev call: state:RING, sip_state:180, iHolded:false, incoming:false
			New  call: state:TALK, sip_state:200, iHolded:false, incoming:false
			*/
			this.log('glueNotify matched outbound call transitioning from RING to TALK (outbound answered by remote)')
			this.outboundAnsweredUICallback(call)

		}
		else if( call.state == "FINISH" && call.sip_state == "404" ) //outbound dial failed callee not found
		{

			this.log('glueNotify matched outbound call bad number 404')
			//TODO: handle if this was a second outbound.
			this.outboundFailedUICallback(call.sip_state,"Invalid number",call.callee)


		}
		else if( pCall.state == "HOLD" && call.state == "TALK")
		{

			if( pCall.remoteHold != undefined && pCall.remoteHold )
			{
				this.log("glueNotify matched remote HOLD -> TALK")
				pCall.remoteHold = undefined
				delete pCall.remoteHold
				this.remoteRetrievedUICallback(call)

			}
			else
			{

				this.log("glueNotify matched local HOLD -> TALK")
				this.localRetrievedUICallback(call)

			}

		}
		else if( call.incoming && call.state == "TALK" ) //inbound call answered
		{

			this.log('glueNotify matched new inbound call talking')
			//TODO: handle if this was a second outbound.
			this.inboundAnsweredUICallback(call)


		}
		else if( !call.incoming && call.state == "TALK" ) //outbound call answered
		{

			this.log('glueNotify matched new outbound call talking')
			//TODO: handle if this was a second outbound.
			this.outboundAnsweredUICallback(call)


		}
		else if( (pCall.state == "RING" || pCall.state == "RINGING" ) && call.state == "FINISH" && call.state == "STATE_IDLE")
		{
			/*
			Prev state:RING, sip_state:-1, iHolded:false, incoming:true
			New  state:FINISH, sip_state:-1, iHolded:false, incoming:true
			*/
		}
		//call.state:FINISH, call.sip_state:-1, call.iHolded:false, call.incoming:true
		else if( call.incoming && (pCall.state == "RING" || pCall.state == "RINGING" ) && call.state == "FINISH" )
		{

			this.log('glueNotify matched new inbound call was ringing now finished (other party hung up before local answered) (missed call)')
			this.missedCallUICallback(call)

		}
		else if( call.state == "HOLD" )
		{

			/*
			when remote party holds call
			Prev state:RING, sip_state:-1, iHolded:false, incoming:true
			New state:HOLD, sip_state:-1, iHolded:false, incoming:true
			*/
			if( pCall.localHold != undefined && pCall.localHold )
			{
				this.log("glueNotify matched local HOLD pCall.localHold=" + pCall.localHold)
				pCall.remoteHold = false
				this.localHeldUICallback(call)
			}
			else
			{

				this.log("glueNotify matched remote HOLD pCall.localHold=" + pCall.localHold)
				pCall.remoteHold = true
				this.remoteHeldUICallback(call)

			}


		}
		else if( call.state == "FINISH" )
		{

			this.log('glueNotify matched call finished')
			this.log("pCall.localHangup=" + pCall.localHangup)
			currentCall = this.getCurrentCall()
			if( currentCall.id != call.id )
			{
				
				this.log("FINISH id not matched with currently talking call so ignorning.")
				return
				
			}
			else if( pCall.localHangup != undefined && pCall.localHangup == true )
			{
				this.log("Matched local hangup notify, so just complete local hangup")
				this.localHungupUICallback(call)

			}
			else
			{

				this.log("Matched remote hangup notify")
				this.remoteHungupUICallback(call)

			}

		}
		else if( call.state == "BUSY" )
		{
			this.log('glueNotify matched call busy')
			this.outboundFailedUICallback(call.sip_state,"Number Busy",call.callee)
		}
		else
		{

			this.log("uncaught notify event, work out what is happening at this state change and handle properly in the ui")
			this.notifyUICallback(call)

		}

		//update call state of this call in the calls array
		//this.log("updating call state from: " + pCall.state + " to: " + call.state)
		pCall.state = call.state
		//this.log("pCall state now: " + pCall.state)
		//this.log(this.currentCalls)
		//this.currentCalls[ call.id ] = $.extend(this.currentCalls[ call.id ], call)


	}

	Phone.prototype.inboundRingingUICallback = function(call) { this.log('this event must be overloaded in the UI: inboundRingingUICallback(call)'); this.log(call); }
	Phone.prototype.inboundAnsweredUICallback = function(call) { this.log('This event must be overloaded in the UI:inboundAnsweredUICallback(call)'); this.log(call) }
	Phone.prototype.outboundRingingUICallback = function(call) { this.log('This event must be overloaded in the UI:outboundRingingUICallback(call)'); this.log(call); }
	Phone.prototype.outboundAnsweredUICallback = function( call ) { this.log('This event must be overloaded in the UI: outboundAnsweredUICallback(call)'); this.log(call); }
	Phone.prototype.localHeldUICallback = function(call) { this.log('This event must be overloaded in the UI: localHeldUICallback(call)'); this.log(call); }
	Phone.prototype.localRetrieveUICallback = function(call) { this.log('This event must be overloaded in the UI: localRetrieveUICallback(call)'); this.log(call) }
	Phone.prototype.remoteHeldUICallback = function(call) { this.log('This event must be overloaded in the UI: remoteHeldUICallback(call)'); this.log(call); }
	Phone.prototype.remoteRetrieveUICallback = function(call) { this.log('This event must be overloaded in the UI: remoteRetrieveUICallback(callee)'); this.log(call) }
	Phone.prototype.localHungupUICallback = function(call) { this.log('This event must be overloaded in the UI: localHungupUICallback(call)'); this.log(call); }
	Phone.prototype.remoteHungupUICallback = function(call) { this.log('This event must be overloaded in the UI: remoteHungupUICallback(call)'); this.log(call); }
	Phone.prototype.outboundFailedUICallback = function(reasonCode, reasonEnglish, callee) { this.log('This event must be overloaded in the UI: outboundFailedUICallback(reasonCode,reasonEnglish,callee)'); this.log(reasonCode); this.log(reasonEnglish); this.log(callee); }
	Phone.prototype.unhandledNotifyUICallback = function(call) { this.log('This event must be overloaded in the UI: unhandledNotifyUICallback(call)'); this.log(call); }


	//probably redundant because notifies are more acurate
	Phone.prototype.glueNotifyCallbackHold = function(call, isHold)
	{
		//this.log("glueNotifyCallbackHold called: (call,isHold), isHold=" + isHold);
	}



/******************************************************************************************
Misc.
*******************************************************************************************/

	Phone.prototype.glueNotifyCost = function(cost)
	{
		//this.log('glueNotifyCost called from RTMP server via flash connector' + cost)
	}
	//Phone.prototype.costUICallback = function(cost) { //this.log('this event must be overloaded in the UI: costCallback(cost)') }

	Phone.prototype.glueNotifyBalance = function(balance)
	{
		//this.log('glueNotifyBalance called from RTMP server via flash connector: $' + balance)
	}
	//Phone.prototype.balanceUICallback = function(cost) { //this.log('this event must be overloaded in the UI: balanceCallback(cost)') }

	Phone.prototype.glueNotifyVideoFormat = function(call)
	{

		//this.log('glueNotifyVideoFormat called: (call)')
		//this.log(call)


	}

	Phone.prototype.glueNotifyMessage = function(messageObject, sipObject)
	{

		//this.log('glueNotifyMessage(messageObject) called: ')
		//this.log(messageObject)
		this.newMessageUICallback(messageObject, sipObject)

	}
	Phone.prototype.newMessageUICallback = function(messageObject, sipObject) { this.log('this event must be overloaded in the UI: newMessageUICallback(messageObject, sipObject)'); this.log(messageObject, sipObject) }

	Phone.prototype.glueNotifyVersion = function(version)
	{

		this.log('glueNotifyVersion: ' + version)

	}

	//returns info about currently used account and status
	Phone.prototype.getInfoAboutMe = function()
	{

		return this.params.flashConnectorHandle.getInfoAboutMe()

	}

	Phone.prototype.getParameters = function()
	{

		return this.params.flashConnectorHandle.getParameters()

	}

	Phone.prototype.getFlashConnectorHandle = function()
	{

		if( this.params.flashConnectorHandle == null )
			this.log( 'Warning: flashConnectorHandle is null' )
		return this.params.flashConnectorHandle

	}

	Phone.prototype.setFlashConnectorHandleFromId = function( id )
	{

		var isIE = navigator.appName.indexOf("Microsoft") != -1;
		this.params.flashConnectorHandle = (isIE) ? window[id] : document[id];
		if( this.params.flashConnectorHandle == null )
			this.log( 'Warning: flashConnectorHandle is still null' )
		return this.getFlashConnectorHandle()

	}

	Phone.prototype.glueNotifyError = function(error)
	{

		if( this.errorHashEN[error] == undefined )
		{

			$phone.log('Error: ' + error)
			this.errorCallback(error)

		}
		else
		{

			$phone.log('Error: ' + error + ': ' + this.errorHashEN[error])
			this.errorUICallback(this.errorHashEN[error])

		}

	}
	Phone.prototype.errorUICallback = function(error) { this.log('this event must be overloaded in the UI: errorUICallback(' + error + ')'); }

	Phone.prototype.log = function( msg ) { if( this.params.debug != true ) return; if( window.console ) console.log( msg ) }

	Phone.prototype.addLogMessage = function( msg )
	{

		this.log( msg )

	}

	/* Flashphoner still to implement
	Phone.prototype.onShowingSettings = function( event )
	{

		this.flashAuthAlreadyPoped = true
		this.showSettingsUICallback(event)

	}
	Phone.prototype.showSettingsUICallback = function( event ) { this.log('this event must be overloaded in the UI: showSettingsUICallback(event):'); this.log(event) }
	*/

	Phone.prototype.shutdown = function()
	{

		clearInterval( this.timerHandle )

	}

	Phone.prototype.getCurrentCalls = function()
	{

		return this.currentCalls

	}

	Phone.prototype.getCallFromId = function(callId)
	{

		matchedCall = null
		$.each( this.currentCalls, function(id,call)
		{

			if( call.id == callId )
			{

				matchedCall = call
				return false
			}

		})
		return matchedCall

	}

	Phone.prototype.getCallIdFromCallee = function(callee)
	{

		var matchedId = null
		$.each( this.currentCalls, function(id,call)
		{

			if( call.callee == callee)
			{

				matchedId = call.id
				return false//N.B. return only breaks out of .each

			}

		})
		return matchedId

	}

	Phone.prototype.getCurrentlyRingingInboundCall = function()
	{

		//this.log("getCurrentlyRingingInboundCall called")
		//this.log(this.currentCalls)
		var matchedCall = null
		$.each( this.currentCalls, function(id,call)
		{

			if( call.incoming && (call.state == "RINGING" || call.state == "RING") )
			{

				matchedCall = call
				return

			}

		})
		if( matchedCall != null)
			return matchedCall
		else
			return null //no matched Call

	}

	Phone.prototype.getCurrentlyRingingOutboundCall = function()
	{

		//this.log("getCurrentlyRingingOutboundCall called")
		//this.log(this.currentCalls)
		var matchedId = null
		$.each( this.currentCalls, function(id,call)
		{

			if( !call.incoming && (call.state == "RINGING" || call.state == "RING") )
			{

				matchedId = call.id
				return

			}

		})
		if( matchedId != null)
			return this.currentCalls[matchedId]
		else
			return null //no matched Call

	}

	Phone.prototype.getCurrentlyTalkingCall = function()
	{

		//this.log("getCurrentlyTalkingCall called")
		//this.log(this.currentCalls)
		var matchedCall = null
		$.each( this.currentCalls, function(id,call)
		{

			if( call.state == "TALKING" || call.state == "TALK" )
			{

				matchedCall = call
				return false //break out of the $.each

			}

		})
		return matchedCall

	}

	Phone.prototype.getCurrentCall = function()
	{

		if( this.currentCalls.length > 0)
			return this.currentCalls[0]
		else
			return null // no matched Call

	}

	Phone.prototype.addCallToCurrentCalls = function( call )
	{

		this.currentCalls[this.currentCalls.length] = call

	}

	Phone.prototype.deleteCallById = function( callId )
	{

		_this = this
		$.each( this.currentCalls, function(id,call)
		{

			if( call.id == callId )
			{

				_this.currentCalls.splice(id,1) 																												//remove 1 element from position id
				return true
			}

		})
		return false

	}

/*************************************************************************************
Connection initialization methods
**************************************************************************************/

	Phone.prototype.flashInstantiated = function(e)
	{

		//Gets called be the Browser Flash plugin then the flash object is initialized and supplies reference to the object id (opportunidy to save this id for later refernece)
		//this.log('flashInitiated called.')
		if( e !== undefined )
		{

			if( e.success == true )
			{

				this.params.flashConnectorHandle = e.ref

			}
			else
			{

				this.log('Failed getting ID of new Flash connector.')

			}

		}

	}

	//Called when swf app is loaded and ready for js calls (e.g. login )
	Phone.prototype.glueNotifyFlashReady = function()
	{

		//this.log('glueNotifyFlashReady');
		//try login imediately		
		this.SIPLogin()
		//also set up repeated login attempts every 25 seconds until SIP success cancels repeated attempts
		this.timerHandle = setInterval( function(){ $phone.SIPLogin() }, 25000 );

	}

	Phone.prototype.SIPLogin = function()
	{

		//calling flash.login triggers the RTMP connection, sip logon failure, or SIP logoff causes RTMP disconnect
		//this.log('Attempting to login to SIP voice/video server.')
		if( this.params.flashConnectorHandle == null || this.params.flashConnectorHandle.login == undefined )
		{

			this.log('flash connector ID not set. registration failed.')
			return false

		}
		else
		{
			//if( this.timerHandle !== null )
			//	clearTimeout(this.timerHandle)
			//var _this = this
			//this.timerHandle = setTimeout( function(){ _this.SIPLogin() }, 25000 );

			//N.B. .login appears to be blocking if RTMP connected, but SIP Server unreachable
			this.params.flashConnectorHandle.loginByToken( this.params.authToken );
		}
	}

	Phone.prototype.wrapPhoneNumbersInDivClass = function(raw, className)
	{
		if(!raw) return ''

		if( className == undefined )
			className = "bodyMatchedPhoneNumber"
		//find and wrap all phone numbers in a div class so that we can bind an intents qtip to it.
		var phoneRegex1 = /(\+?\d{1,3})?[- \.]?(\(\d{1,3}\)|\d{1,3})[- \.]?\d{2,4}[- \.]?\d{2,4}/ig;
		return raw.replace(phoneRegex1,function(part) { return " <div class=\"" + className + "\" style=\"display:inline; cursor:pointer; text-decoration:underline;\">" + part.replace(/^\s\s*/, '').replace(/\s\s*$/, '') + "</div>" })


	}

	Phone.prototype.glueNotifyRTMPConnected = function()
	{

		this.log("glueNotifyRTMPConnected: connected to RTMP server");
		//check if mic enabled and show seciruty settings if not.
		//if( this.params.flashConnectorHandle )
		//hide flash panel until needed in video call
		this.registrationState = 'RTMPconnected'
		if( this.timerHandle !== null )
			clearInterval(this.timerHandle)
		this.timerHandle = null

	}

	Phone.prototype.glueNotifySIPLoginSuccess = function(sipObject)
	{

		this.log("glueNotifySIPLoginSuccess: SIP account logged in")
		this.registrationState = 'SIPregistered'
		if( this.timerHandle !== null )
			clearInterval(this.timerHandle)
		this.timerHandle = null
		this.SIPLoginSuccessUICallback(sipObject)
		this.log( 'jquery.flashphoner.js #1037: Finished glueNotifySIPLoginSuccess.' )
		
	}
	
	Phone.prototype.glueNotifySubscription = function(subscribeObject,sipObject)
	{
		
		if( subscribeObject.event == "reg" && subscribeObject.status == "Terminated" )
			this.notifyUISIPAccountLoggedOut()
		
	}
	
	Phone.prototype.notifyUISIPAccountLoggedOut = function() { this.log( 'jquery.flashphoner.js #1047: Overload this method on the UI layer to handle this event' ) }

	Phone.prototype.glueNotifyAgc = function(agc)
	{

		//this.log( 'jquery.flashphoner.js #1061: agc.gain:' + agc.gain )

	}

	Phone.prototype.setProperty = function(key,value)
	{

		return this.params.flashConnectorHandle.setProperty(key,value)

	}

	Phone.prototype.setSpeexQuality = function(i)
	{

		this.params.flashConnectorHandle.setSpeexQuality(i);

	}

	Phone.prototype.forceClientCodec = function(codec)
	{

		if( codec.toLowerCase().indexOf("speex") != -1 )
			this.params.flashConnectorHandle.setProperty("local_audio_codec","speex16");
		else if( codec.toLowerCase().indexOf("g711") != -1 || codec.toLowerCase().indexOf("default") != -1 )
			this.params.flashConnectorHandle.setProperty("local_audio_codec","default");
		else
			this.log( 'jquery.flashphoner.js #1002: specified codec not matched: ' + codec )

	}

	Phone.prototype.setJitterBufferEnabled = function(enabled)
	{

		if( enabled )
		{

			this.params.flashConnectorHandle.setProperty("in_jitter_buffer_enabled","true");
			this.params.flashConnectorHandle.setProperty("out_jitter_buffer_enabled","true");

		}
		else
		{

			this.params.flashConnectorHandle.setProperty("in_jitter_buffer_enabled","default");
			this.params.flashConnectorHandle.setProperty("out_jitter_buffer_enabled","default");

		}

	}

	//will be needed if we ever set up an "enabled" button
	Phone.prototype.SIPLoginSuccessUICallback = function() { this.log('this event must be overloaded in the UI: SIPLoginSuccessUICallback') }

	Phone.prototype.glueNotifySIPLoginProgress = function( SIPLoginProgressState )
	{

		if( SIPLoginProgressState )
		{

			//this.log('SIP account not logged in, trying to login.')
			this.registrationState = 'SIPunregistered'

		}
		else
		{

			this.log('glueNotifySIPLoginProgress: ' + SIPLoginProgressState )
			clearInterval(this.timerHandle)
			this.timerHandle = null

		}
		//this.SIPLoginProgressUICallback( SIPLoginProgressState )

	}
	//Phone.prototype.SIPLoginProgressUICallback = function(SIPLoginProgressState) { this.log('this event must be overloaded in the UI: SIPLoginProgressUICallback('+SIPLoginProgressState+')') }

	Phone.prototype.SIPLogOff = function()
	{

		this.registrationState = 'SIPunregistered'
		this.log("SIPLogOff");
		this.params.flashConnectorHandle.logoff();

	}

	Phone.prototype.glueNotifyLostRTMPConnection = function() 
	{

		this.log("Lost connection to RTMP server");
		this.registrationState = 'RTMPdisconnected'
		//var _this = this
		//setTimeout( "_this.SIPLogin();",10000);
		//set up attempt to reconnect
		if( this.timerHandle == null )
			this.timerHandle = setInterval( function(){ $phone.SIPLogin() }, 25000 );
		
		this.lostRTMPConnectionUICallback()

	}
	Phone.prototype.lostRTMPConnectionUICallback = function() { this.log('this event must be overloaded in the UI: lostRTMPConnectionUICallback()') }

	Phone.prototype.hideFlashStageWrapper = function() { this.log('This must be overloaded in UI layer to hide the Flash Stage Wrapper.') }
	Phone.prototype.showFlashStageWrapper = function() { this.log('This must be overloaded in UI layer to show the Flash Stage Wrapper.') }

	Phone.prototype.encodeId = function(calleeName) {
	    return calleeName.replace(":","abcdefgh").replace("@","ijklmn").replace(".","opqrstu");
	}

	//returns calleName
	Phone.prototype.decodeId = function(calleeNameId){
	   return calleeNameId.replace("abcdefgh",":").replace("ijklmn","@").replace("opqrstu",".");
	}

	Phone.prototype.generateUUID = function()
	{
		var d = new Date().getTime()
		var uuid = 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c)
		{
			var r = (d + Math.random()*16)%16 | 0
			d = d/16 | 0
			return (c=='x' ? r : (r&0x7|0x8)).toString(16)
		})
		return uuid

	}
	
	Phone.prototype.sendXcapRequest = function(uri)
	{
		
		this.params.flashConnectorHandle.sendXcapRequest("http://your.xcap.server.com:9110/services/org.openmobilealliance.deferred-list/users/sip:" + this.params.userId + "@" + this.params.domain + "/deferred-list")
		
	}
	
	Phone.prototype.glueNotifyXcapResponse = function(body)
	{

		//this.log( 'jquery.flashphoner.js #1189: glueNotifyXcapResponse::body:' )
		//this.log( body )
		this.notifyXcapResponseUICallback(body)
		
	}
	
	Phone.prototype.notifyXcapResponseUICallback = function(body) { this.log('this event must be overloaded in the UI: notifyXcapResponseUICallback(body)') }
	

})(jQuery)

//Events called from Flash
//------------------------
//notifyCloseConnection():void 							When the connection to the server is lost
//notifyConnected():void 								Application connected to the server
//notifyRegistered():void 								User registered on sip server
//notifyCall(call:Call):void 							Incoming call _sipObject is sip-headers of request and response.
//notifyError(error:String):void 						error with error code
//notifyCost(call:Call,cost:Number):void 				cost of current call
//notifyBalance(balance:Number):void 					Balance of user
//notify(call:Call):void 								State of call

//notifyCallbackHold(call:Call,isHold:Boolean):void		Callback for operation - setStatusHold(callId:String,isHold:Boolean = true):void

//notifyVideoFormat(call:Call):void 					Notify video height and width of incomming and outgoing video
//notifyMessage(messageObject:Object, sipObject:Object):void 				Notify of the new instance message
//notifyAddCall(call:Call):void 						Notify add new call in array
//notifyRemoveCall(call:Call):void 						Notify remove call from array
//addLogMessage(message:String):void					Add log message (example: for warnings)
//notifyVersion(version:String):void					Notify change version
//notifyFlashReady():void 								Notify if swf file and flashphoner.xml loaded
//notifyOpenVideoView(isViewed):void

//Flash methods that can be called
//--------------------------------
//getParameters():object 						Return object of properties from flashphoner.xml file
//login(username:String,password:String):int 	Login to flashphoner_server, returned 0 - if 'username' is success, 1 - if 'username' is wrong.
//												Format 'username': "sip:XXXX@XXXX.XXX"
//loginWithToken(token:String):void				Login to flashphoner_server by token which use in http-provider
//loginClick2Call():void 						Login to user with login from server by parameter of file flashphoner.properties from server side.
//logoff():void									Log off current user
//getInfoAboutMe():ModelLocator					Get info about logged user (login,password,sipAddress,sipPort,visibleName)
//sendMessage(to:String, multiTo:String, body:String, contentType:String):void	Send instance message to user with login 'callee'
//call(callee:String, visibleName:String, isVideoCall:Boolean = true):int 	Call to user with login 'callee',
//												Returned 0 - if 'callee' is success, 1 - if 'callee' is wrong.
//												Format 'callee': "sip:XXXX@XXXX.XXX" or sip login or phone number or null. if 'callee' is null then 'callee' equals parameter of file flashphoner.properties from server side.
//hangup(callId:String):void 					Hangup current call
//answer(callId:String, isVideoCall:Boolean = false):void	Answer on incomming call
//sendDTMF(callId:String,dtmf:String):void		Send DTMF command
//setStatusHold(callId:String,isHold:Boolean = true):void	Hold/Unhold for the call
//transfer(callId:String,target:String):void	Transfer call  to the user with login 'target'
//setSendVideo(isSend:Boolean):void	Start/Stop sending video
//getMicVolume():int 							Get current microphone volume
//setMicVolume(volume:int):void 				Set volume current microphone
//getVolume():int								Get current speaker volume
//setVolume(volume:int):void					Set current speaker volume
//isMuted():int									Check access to the microphone.
//												Return 0 - if microphone does no exist, -1 - if has access, 1 - if has not access
//getMicropones():Array 						Get array of names all microphones
//setMicrophone(name:String):void{} 			Use named micropohne
//getCameras():Array 							Get array of names all cameras
//setCamera(name:String):void{} 				Use named camera
//getCurrentCall():Call							Get current call in TALK state
//setCookie(key:String,value:String):void 		Set cookie for save info
//getCookie(key:String):String 					Get cookie for load info
//getVersion():String 							Get version of client and server. Version of the server shows if user logged

//Call
//----
//Property 						Desription
//id:String; 					Сall identifier
//state:String; 				Call status (TALK,RING,HOLD...)
//iHolded:Boolean = false; 		Is holded call
//callee:String; 				User, which created call
//caller:String; 				User, which answered call
//visibleNameCallee:String; 	Visible name of callee
//visibleNameCaller:String; 	Vilible name of caller
//playerVideoHeight:int; 		Height of video in player
//playerVideoWidth:int; 		Width of video in player
//streamerVideoHeight:int; 		Height of video in streamer
//streamerVideoWidth:int; 		Width of video in player
//timeOfCall:int = 0; 			Current time of call
//anotherSideUser:String; 		A user on the other side
//incoming:Boolean = false; 	Is incoming call
//isVideoCall:Boolean = false; 	Is video call

//subscribe(subscribeObject:Object):void	Send SUBSCRIBE request to subscribe for particular event. reg event is only supported now.
//notifySubscription(subscribeObject,sipObject):void	Notification about the subscription state


//Constants
//---------
//ErrorCodes
//----------
//AUTHENTICATION_FAIL 			Registration on sip-server is fail after sending login request.
//USER_NOT_AVAILABLE 			User not available in sip server
//TOO_MANY_REGISTER_ATTEMPTS 	User with name many register
//LICENSE_RESTRICTION 			License of flashphoner is not valid
//INTERNAL_SIP_ERROR 			Unknown sip error
//CONNECTION_ERROR 				Connection to the server fail
//REGISTER_EXPIRE 				Registration on sip server is expired
//
//Call states
//-----------
//STATE_RING 					Start ring of call
//STATE_TALK 					Talk command for send and rerceive audio and video
//STATE_FINISH 					Finish of current call
//STATE_BUSY 					Busy of current call
//STATE_HOLD					Hold of current call
