Ext.ns("Terrasoft.integration");
Ext.ns("Terrasoft.integration.telephony");
Ext.ns("Terrasoft.integration.telephony.oktell");

//region Class: OktellCtiProvider

/**
 * The provider class to the Oktell message service.
 */
Ext.define("Terrasoft.integration.telephony.oktell.OktellCtiProvider", {
	extend: "Terrasoft.integration.telephony.BaseCtiProvider",
	alternateClassName: "Terrasoft.OktellCtiProvider",
	singleton: true,

	//region Properties: Private

	/**
  * Our phone number.
  * @type {String}
  */
	deviceId: "",

	/**
  * The active call object.
  * @private
  * @type {Terrasoft.integration.telephony.Call}
  */
	activeCall: null,

	/**
  * Object of consulting call.
  * @private
  * @type {Terrasoft.integration.telephony.Call}
  */
	consultCall: null,

	/**
  * A sign that the SIP end device supports the ability to answer a call.
  * @private
  * @type {Boolean}
  */
	isSipAutoAnswerHeaderSupported: true,

	/**
  * A flag that the user is a call center operator.
  * @private
  * @type {Boolean}
  */
	isOperator: false,

	/**
  * The default port for the server-based http Oktell service.
  * @private
  * @type {Number}
  */
	oktellServerServiceDefaultPort: 4055,

	/*jshint bitwise:false */
	/**
  * The set of default operations {@link Terrasoft.CallFeaturesSet} is in a call state.
  * @private
  * @type {Number}
  */
	defaultTalkingStateCallFeatures: Terrasoft.CallFeaturesSet.CAN_HOLD | Terrasoft.CallFeaturesSet.CAN_DROP | Terrasoft.CallFeaturesSet.CAN_MAKE_CONSULT_CALL | Terrasoft.CallFeaturesSet.CAN_BLIND_TRANSFER,
	/*jshint bitwise:true */

	//endregion

	//region Properties: Protected

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#licInfoKeys
  * @protected
  * @override
  * @type {String[]}
  */
	licInfoKeys: ["BPMonlineOktellConnector.Use"],

	//endregion

	//region Methods: Private

	/**
  * Method of opening a connection.
  * @private
  * @param {Object} config connection parameters.
  */
	connect: function (config) {
		this.isSipAutoAnswerHeaderSupported = config.isSipAutoAnswerHeaderSupported !== false;
		this.isOperator = config.isOperator !== false;
		this.subscribeOnOktellEvents();
		window.oktell.connect({
			url: config.url,
			login: config.login,
			password: config.password,
			debugMode: config.debugMode,
			webSocketSwfLocation: config.webSocketSwfLocation
		});
	},

	/**
  * Method for subscribing to Oktell events.
  * @private
  */
	subscribeOnOktellEvents: function () {
		window.oktell.on("connect", this.onConnect, this);
		window.oktell.on("connectError", this.onConnectError, this);
		window.oktell.on("disconnect", this.onDisconnect, this);
		window.oktell.on("statusChange", this.onStatusChange, this);
		window.oktell.on("ringStart", this.onRingStart, this);
		window.oktell.on("ringStop", this.onRingStop, this);
		window.oktell.on("backRingStart", this.onBackRingStart, this);
		window.oktell.on("backRingStop", this.onBackRingStop, this);
		window.oktell.on("callStart", this.onCallStart, this);
		window.oktell.on("callStop", this.onCallStop, this);
		window.oktell.on("talkStart", this.onTalkStart, this);
		window.oktell.on("talkStop", this.onTalkStop, this);
		window.oktell.on("holdAbonentLeave", this.onHoldAbonentLeave, this);
		window.oktell.on("holdAbonentEnter", this.onHoldAbonentEnter, this);
		window.oktell.on("holdStateChange", this.onHoldStateChange, this);
		window.oktell.on("stateChange", this.onCallStateChange, this);
		window.oktell.on("abonentsChange", this.onAbonentsChange, this);
		window.oktell.onNativeEvent("flashstatechanged", this.onFlashStateChanged.bind(this));
		window.oktell.onNativeEvent("userstatechanged", this.onUserStateChanged.bind(this));
	},

	/**
  * The method of unsubscribing from the Oktell events.
  * @private
  */
	unsubscribeFromOktellEvents: function () {
		window.oktell.off("connect");
		window.oktell.off("connectError");
		window.oktell.off("disconnect");
		window.oktell.off("statusChange");
		window.oktell.off("ringStart");
		window.oktell.off("ringStop");
		window.oktell.off("backRingStart");
		window.oktell.off("backRingStop");
		window.oktell.off("callStart");
		window.oktell.off("callStop");
		window.oktell.off("talkStart");
		window.oktell.off("talkStop");
		window.oktell.off("holdAbonentLeave");
		window.oktell.off("holdAbonentEnter");
		window.oktell.off("holdStateChange");
		window.oktell.off("stateChange");
		window.oktell.off("abonentsChange");
		window.oktell.offNativeEvent("flashstatechanged", this.onFlashStateChanged);
		window.oktell.offNativeEvent("userstatechanged", this.onUserStateChanged);
	},

	/**
  * Create a call id for the caller information.
  * @private
  * @return {String} Call Id.
  */
	getCallId: function (abonent) {
		var chainId = abonent.chainId;
		if (!abonent.phone) {
			return chainId;
		}
		var number = abonent.phone.replace(/[^\d]/g, "");
		if (!number) {
			return chainId + ":" + abonent.phone;
		}
		number = parseInt(number, 10) !== 0 ? number.replace(/^0+/g, "") : "0";
		return chainId + ":" + number;
	},

	/**
  * Select the call chainId from the call identifier.
  * @private
  * @param {String} callId Call id.
  * @return {String} The identifier of the chain of commutation.
  */
	extractChainId: function (callId) {
		if (Ext.isEmpty(callId)) {
			return null;
		}
		var callIdParts = callId.split(":");
		if (callIdParts.length < 2) {
			return null;
		}
		return callIdParts[0];
	},

	/**
  * Gets the address of the server-side http Oktell service.
  * @private
  * @return {String} The address of the server-side http Oktell service.
  */
	getOktellServerHttpUrl: function () {
		var connectionConfig = this.initialConfig.connectionConfig;
		var webSocketUrl = connectionConfig.url;
		if (Ext.isEmpty(webSocketUrl)) {
			throw new Terrasoft.ArgumentNullOrEmptyException({
				argumentName: "connectionConfig.url"
			});
		}
		return webSocketUrl.replace(/^ws/, "http");
	},

	/**
  * Gets a temporary password to execute requests from the server-side http Oktell service.
  * @private
  * @param {Function} callback Callback function.
  * @param {String} callback.password Temporary password for querying the server-side http Oktell service.
  */
	getOktellServerHttpTempPassword: function (callback) {
		window.oktell.exec("gettemphttppass", null, function (response) {
			if (response.result === true) {
				callback(response.password);
			} else {
				var errorInfo = {
					internalErrorCode: -1,
					data: this.Terrasoft.encode(response),
					source: "oktell.gettemphttppass",
					errorType: Terrasoft.MsgErrorType.COMMAND_ERROR
				};
				this.fireEvent("error", errorInfo);
				callback();
			}
		}.bind(this));
	},

	/**
  * Creates an active or consulting call.
  * @private
  * @param {Object} abonent The object oktell abonent
  * @param {Terrasoft.CallDirection} direction Direction of the call.
  * @param {boolean} isConsultCall Indicates a consulting call.
  * @return {Terrasoft.integration.telephony.Call}
  */
	createCall: function (abonent, direction, isConsultCall) {
		var call = Ext.create("Terrasoft.OktellCall");
		//TODO: ChainId не уникален. Совпадает для консультационного и активного звонка

		call.id = this.getCallId(abonent);
		call.chainId = abonent.chainId;
		call.direction = direction;
		call.deviceId = this.deviceId;
		if (direction === Terrasoft.CallDirection.IN) {
			call.callerId = abonent.phone;
			call.calledId = this.deviceId;
		} else {
			call.callerId = this.deviceId;
			call.calledId = abonent.phone;
		}
		call.ctiProvider = this;
		call.timeStamp = new Date();
		call.callFeaturesSet = Terrasoft.CallFeaturesSet.CAN_NOTHING;
		if (isConsultCall) {
			call.redirectingId = this.deviceId;
			call.redirectionId = direction === Terrasoft.CallDirection.OUT ? call.calledId : call.callerId;
			if (this.consultCall) {
				call.databaseUId = this.consultCall.databaseUId;
				this.fireEvent("callInfoChanged", call);
			}
			this.consultCall = call;
		} else {
			//TODO: Получать список доступных команд из объекта Oktell (getPhoneActions)

			call.callFeaturesSet = Terrasoft.CallFeaturesSet.CAN_DROP;
			this.activeCall = call;
		}
		if (direction === Terrasoft.CallDirection.IN && this.isSipAutoAnswerHeaderSupported) {
			/*jshint bitwise:false */
			call.callFeaturesSet |= Terrasoft.CallFeaturesSet.CAN_ANSWER;
			/*jshint bitwise:true */
		}
		call.state = Terrasoft.GeneralizedCallState.ALERTING;
		//TODO: Определить, что звонок - консультационный на основании данных Oktell

		this.updateDbCall(call, this.onUpdateDbCall);
		return call;
	},

	/**
  * Ends the active or consulting call.
  * @private
  * @param {String} callId Call id.
  */
	finishCall: function (callId) {
		this.log("finishCall {0}", callId);
		var call;
		if (Ext.isEmpty(callId)) {
			call = this.activeCall;
			this.activeCall = null;
		} else {
			if (!Ext.isEmpty(this.activeCall) && this.activeCall.id === callId) {
				call = this.activeCall;
				this.activeCall = null;
			} else if (this.getIsCurrentConsultCall(callId)) {
				call = this.consultCall;
				this.consultCall = null;
			}
		}
		if (Ext.isEmpty(call)) {
			return;
		}
		call.oldState = call.state;
		call.state = Terrasoft.GeneralizedCallState.NONE;
		call.timeStamp = new Date();
		this.fireEvent("callFinished", call);
		if (!Ext.isEmpty(this.activeCall)) {
			this.fireEvent("lineStateChanged", { callFeaturesSet: this.activeCall.callFeaturesSet });
		} else {
			if (!Ext.isEmpty(this.consultCall)) {
				// The initial call was completed, but we are in consultation mode
				this.activeCall = this.consultCall;
				this.consultCall = null;
			} else {
				this.fireEvent("lineStateChanged", { callFeaturesSet: call.callFeaturesSet });
			}
		}
		this.updateDbCall(call, this.onUpdateDbCall);
	},

	/**
  * Changes the status of the operator activity in the Call Center.
  * @param {Boolean} isActive The flag of the activity of the operator in the Call Center.
  * @private
  */
	setCallCenterState: function (isActive) {
		window.oktell.exec("setuserstate", {
			"oncallcenter": isActive
		});
	},

	/**
  * Returns a flag that the call is a current call.
  * @private
  * @param {String} callId Call id.
  * @return {Boolean} A flag that the call is a current call.
  */
	getIsCurrentConsultCall: function (callId) {
		if (!Ext.isEmpty(this.consultCall)) {
			var callChainId = this.extractChainId(callId);
			return this.consultCall.chainId === callChainId;
		}
		return false;
	},

	/**
  * Updates the consultation call after the conversation with the consultant.
  * @private
  * @param {String} number Consultant's telephone number.
  */
	updateConsultCallOnTalkStarted: function (number) {
		if (this.consultCall.calledId !== number) {
			this.consultCall.calledId = number;
			this.consultCall.redirectionId = number;
			this.activeCall.redirectionId = number;
		}
		this.activeCall.callFeaturesSet = Terrasoft.CallFeaturesSet.CAN_COMPLETE_TRANSFER;
	},

	/**
  * Initializes the user status in the Call Center.
  * @private
  */
	initCallCenterState: function () {
		this.setCallCenterState(this.isOperator === true);
	},

	/**
  * Returns the user's ability to make an outgoing call.
  * @param {Terrasoft.OktellAgentState} userState User State Oktell.
  * @return {Boolean}
  */
	userCanDial: function (userState) {
		return [Terrasoft.OktellAgentState.READY, Terrasoft.OktellAgentState.BREAK].indexOf(userState) !== -1;
	},

	/**
  * Handles the storage of information about the call to the database.
  * @private
  * @param {Object} request Instance of the request.
  * @param {Boolean} success Indicates a successful server response.
  * @param {Object} response Server response.
  */
	onUpdateDbCall: function (request, success, response) {
		var callDatabaseUid = Terrasoft.decode(response.responseText);
		if (success && Terrasoft.isGUID(callDatabaseUid)) {
			var call = Terrasoft.decode(request.jsonData);
			if (!Ext.isEmpty(this.activeCall) && this.activeCall.id === call.id) {
				call = this.activeCall;
			} else if (this.getIsCurrentConsultCall(call.id)) {
				call = this.consultCall;
			}
			call.databaseUId = callDatabaseUid;
			this.fireEvent("callSaved", call);
		} else {
			this.fireEvent("rawMessage", "Update Call error");
			var errorInfo = {
				internalErrorCode: null,
				data: response.responseText,
				source: "App server",
				errorType: Terrasoft.MsgErrorType.COMMAND_ERROR
			};
			this.fireEvent("error", errorInfo);
		}
	},

	/**
  * Handles the successful connection to the server event.
  * @private
  */
	onConnect: function () {
		this.deviceId = window.oktell.getMyInfo().number;
		this.initCallCenterState();
		this.setWrapUpUserState(false);
		this.fireEvent("rawMessage", "Connected");
		this.fireEvent("initialized");
	},

	/**
  * Handles the connection error event with the server in the connect method.
  * The error codes are the same as for the callback function of the connect method.
  * Possible error codes
  * internalErrorCode data
  * 1200 cant connect to server
  * 1205 error url
  * 1204 using oktell desktop client
  * 1202 error loginpass
  * 1206 error loading version info
  * 1207 error loading phone state
  * 1209 error loading user state
  * 1210 error loading user info
  * 1211 login failure
  * 1212 error connect using session
  * 1213 no password or session
  * 1214 max online users count reached, license limitation
  * @private
  * @param {Object} err Error code.
  */
	onConnectError: function (err) {
		this.fireEvent("rawMessage", "onConnectError: " + Terrasoft.encode(err));
		var errorInfo = {
			internalErrorCode: err.errorCode,
			data: err.errorMessage,
			source: "Oktell server"
		};
		switch (err.errorCode) {
			case 1200:
				errorInfo.errorType = Terrasoft.MsgErrorType.CALL_CENTRE_NOT_AVAILABLE_ERROR;
				break;
			case 1202:
				errorInfo.errorType = Terrasoft.MsgErrorType.AUTHENTICATION_ERROR;
				break;
			default:
				errorInfo.errorType = Terrasoft.MsgErrorType.OPEN_CONNECTION_ERROR;
				break;
		}
		this.fireEvent("error", errorInfo);
	},

	/**
  * Handles the connection closure event with the server.
  * In the callback function, an object is given with a description of the cause of the connection failure.
  * Possible causes
  * code message
  * 10 critical ws method not supported by this version of Oktell server
  * 11 error loading version info
  * 12 websocket connection closed
  * 13 disconnected by user
  * @private
  * @param {Object} reason The cause for closing the connection.
  */
	onDisconnect: function (reason) {
		this.fireEvent("rawMessage", "Disconnected");
		this.fireEvent("disconnected", reason);
		this.unsubscribeFromOktellEvents();
	},

	/**
  * Handles the agent state change event.
  * In the callback function, two string parameters are passed - the new and the past state.
  * Possible statuses:
  * ready  Ready for calls
  * dnd   Do not disturb
  * break  Break
  * redirect Redirection
  * @private
  * @param {String} newStatus New status.
  * @param {String} oldStatus Previous status.
  */
	onStatusChange: function (newStatus, oldStatus) {
		this.fireEvent("rawMessage", "newStatus: " + newStatus + " oldStatus: " + oldStatus);
		this.fireEvent("agentStateChanged", { userState: newStatus });
	},

	/**
  * Handles the events of the beginning of an incoming call.
  * @private
  * @param {Object[]} abonents An array of abonent objects.
  */
	onRingStart: function (abonents) {
		this.fireEvent("rawMessage", "onRingStart: " + Terrasoft.encode(abonents));
		var call = this.createCall(abonents[0], Terrasoft.CallDirection.IN, false);
		this.fireEvent("callStarted", call);
		this.fireEvent("lineStateChanged", { callFeaturesSet: call.callFeaturesSet });
	},

	/**
  * Handles the completion of an incoming call.
  * @private
  * @param {Object[]} abonents An array of abonent objects.
  */
	onRingStop: function (abonents) {
		this.fireEvent("rawMessage", "onRingStop: " + Terrasoft.encode(abonents));
		if (Ext.isEmpty(this.activeCall)) {
			return;
		}
		var state = window.oktell.getState();
		if (state === Terrasoft.OktellLineState.READY) {
			var callId = this.getCallId(abonents[0]);
			this.finishCall(callId);
		}
	},

	/**
  * Handles the events of the callback.
  * @private
  * @param {Object[]} abonents An array of abonent objects
  */
	onBackRingStart: function (abonents) {
		this.fireEvent("rawMessage", "onBackRingStart: " + Terrasoft.encode(abonents));
	},

	/**
  * Handles the callback completion event.
  * @private
  * @param {Object[]} abonents An array of abonent objects.
  */
	onBackRingStop: function (abonents) {
		this.fireEvent("rawMessage", "onBackRingStop: " + Terrasoft.encode(abonents));
	},

	/**
  * Handles the outbound call start event.
  * @private
  * @param {Object} abonents An array of abonent objects (there is always 1 element for the current event).
  */
	onCallStart: function (abonents) {
		this.fireEvent("rawMessage", "onCallStart: " + Terrasoft.encode(abonents));
		var abonent = abonents[0];
		var isConsultationCall = false;
		var activeCall = this.activeCall;
		if (!Ext.isEmpty(activeCall) && abonent.phone !== activeCall.callerId && abonent.phone !== activeCall.calledId) {
			isConsultationCall = true;
		}
		var call = this.createCall(abonent, Terrasoft.CallDirection.OUT, isConsultationCall);
		this.fireEvent("callStarted", call);
		this.fireEvent("lineStateChanged", { callFeaturesSet: call.callFeaturesSet });
	},

	/**
  * Handles the outgoing call end event.
  * @private
  * @param {Object} abonents An array of abonent objects (there is always 1 element for the current event).
  */
	onCallStop: function (abonents) {
		this.fireEvent("rawMessage", "onCallStop: " + Terrasoft.encode(abonents));
		if (Ext.isEmpty(this.activeCall)) {
			return;
		}
		var state = window.oktell.getState();
		var callId;
		if (state === Terrasoft.OktellLineState.READY || state === Terrasoft.OktellLineState.RING) {
			callId = this.getCallId(abonents[0]);
			this.finishCall(callId);
		}
		var holdInfo = window.oktell.getHoldInfo();
		if (state === Terrasoft.OktellLineState.READY && holdInfo.hasHold) {
			// our call was disconnected, the Oktell setting of the phone is set to "Disconnect"
			window.oktell.hold();
		}
		if (state === Terrasoft.OktellLineState.TALK && !holdInfo.hasHold) {
			callId = this.getCallId(abonents[0]);
			if (this.getIsCurrentConsultCall(callId)) {
				// the consultation call was disconnected
				this.finishCall(callId);
			}
		}
	},
	/*jshint bitwise:false */
	/**
  * Handles the start event.
  * @private
  * @param {Object[]} abonents An array of abonent objects.
  */
	onTalkStart: function (abonents) {
		this.fireEvent("rawMessage", "onTalkStart: " + Terrasoft.encode(abonents));
		var abonent = abonents[0];
		var callId = this.getCallId(abonent);
		// the conversation starts without lifting the handset (for example, an auto-upgrade is set on the softphone)
		var activeCall = this.activeCall;
		if (Ext.isEmpty(activeCall)) {
			// emulate the incoming call
			// TODO: определить направление звонка. Сейчас по объекту abonents это сделать нельзя

			this.onRingStart(abonents);
			activeCall = this.activeCall;
		} else if (Ext.isEmpty(this.consultCall) && callId !== activeCall.id) {
			// the advisory call on a softphone with autodetecting of a tube is made. Emulate the outgoing call
			this.onCallStart(abonents);
		}
		var activeCallExists = !Ext.isEmpty(activeCall);
		var call;
		if (activeCallExists && this.activeCall.id === callId) {
			call = activeCall;
		} else if (this.getIsCurrentConsultCall(callId)) {
			this.updateConsultCallOnTalkStarted(abonent.phone);
			call = this.consultCall;
		}
		if (Ext.isEmpty(call)) {
			return;
		}
		call.timeStamp = new Date();
		call.callFeaturesSet = this.defaultTalkingStateCallFeatures;
		call.oldState = call.state;
		call.state = Terrasoft.GeneralizedCallState.CONNECTED;
		if (call.oldState === Terrasoft.GeneralizedCallState.ALERTING) {
			this.fireEvent("commutationStarted", call);
		}
		if (activeCallExists) {
			this.fireEvent("lineStateChanged", { callFeaturesSet: this.activeCall.callFeaturesSet });
		}
		this.updateDbCall(call, this.onUpdateDbCall);
	},

	/**
  * Handles the end event of the conversation.
  * @private
  * @param {Object[]} abonents An array of abonent objects.
  */
	onTalkStop: function (abonents) {
		this.fireEvent("rawMessage", "onTalkStop: " + Terrasoft.encode(abonents));
		var state = window.oktell.getState();
		var holdInfo = window.oktell.getHoldInfo();
		var abonent = abonents[0];
		var callId = !Ext.isEmpty(abonent) ? this.getCallId(abonent) : null;
		if (state === Terrasoft.OktellLineState.TALK) {
			if (!holdInfo.hasHold) {
				if (this.getIsCurrentConsultCall(callId)) {
					this.finishCall(callId);
				} else if (!Ext.isEmpty(this.activeCall) && this.activeCall.id === callId) {
					// if we are the 3rd subscriber and ended the consultation call and there was a transfer to subscriber 1
					this.finishCall(callId);
				}
			}
		} else if (state === Terrasoft.OktellLineState.READY) {
			if (!holdInfo.hasHold) {
				this.finishCall(callId);
				if (!Ext.isEmpty(this.activeCall)) {
					this.finishCall();
				}
			}
		} else if (state === Terrasoft.OktellLineState.CALL && !holdInfo.hasHold) {
			this.finishCall(callId);
		} else if (!Ext.isEmpty(this.activeCall)) {
			if (!holdInfo.hasHold) {
				this.finishCall(callId);
			}
		}
	},

	/**
  * Handles the subscriber's exit event from the hold. In the callback function, an abonent object with information about the subscriber is transmitted.
  * @private
  * @param {Object[]} abonents An array of abonent objects.
  */
	onHoldAbonentLeave: function (abonents) {
		this.fireEvent("rawMessage", "onHoldAbonentLeave: " + Terrasoft.encode(abonents));
	},

	/**
  * Handles the subscriber's entry event in hold. In the callback function, an abonent object with information about the subscriber is transmitted.
  * @private
  * @param {Object[]} abonents An array of abonent objects.
  */
	onHoldAbonentEnter: function (abonents) {
		this.fireEvent("rawMessage", "onHoldAbonentEnter: " + Terrasoft.encode(abonents));
	},

	/**
  * Handles the hold state change event. The callback function provides information on the hold
  * (getHoldInfo).
  * @private
  * @param {Object} holdInfo The holdInfo object.
  */
	onHoldStateChange: function (holdInfo) {
		this.fireEvent("rawMessage", "onHoldStateChange: " + Terrasoft.encode(holdInfo));
		//TODO: Проверять параметры события - для консультационного или активного?

		var call = this.activeCall;
		if (Ext.isEmpty(call)) {
			// the situation is possible if the subscriber has completed the call while being on hold
			var message = "Holded activeCall is empty";
			this.logError("onHoldStateChange: {0}", message);
			return;
		}
		call.timeStamp = new Date();
		if (holdInfo.hasHold) {
			if (call.state !== Terrasoft.GeneralizedCallState.HOLDED) {
				call.callFeaturesSet = Terrasoft.CallFeaturesSet.CAN_UNHOLD;
				call.state = Terrasoft.GeneralizedCallState.HOLDED;
				this.fireEvent("hold", call);
			}
		} else {
			var lineState = window.oktell.getState();
			if (lineState === Terrasoft.OktellLineState.CALL) {
				call.callFeaturesSet = Terrasoft.CallFeaturesSet.CAN_DROP;
				call.state = Terrasoft.GeneralizedCallState.ALERTING;
			} else {
				call.callFeaturesSet = this.defaultTalkingStateCallFeatures;
				call.state = Terrasoft.GeneralizedCallState.CONNECTED;
			}
			this.fireEvent("unhold", call);
		}
		this.updateDbCall(call, this.onUpdateDbCall);
		this.fireEvent("lineStateChanged", { callFeaturesSet: call.callFeaturesSet });
	},

	/**
  * Handles a line status change event.
  * @private
  * @param {String} newState New status.
  * @param {String} oldState Previous status.
  */
	onCallStateChange: function (newState, oldState) {
		this.log("stateChange, newState = {0}, oldState= {1}", newState, oldState);
	},

	/**
  * Handles a low-level event of a hold state change (flash).
  * @private
  * @param {Object} data Event parameters
  */
	onFlashStateChanged: function (data) {
		this.fireEvent("rawMessage", "onFlashStateChanged: " + Terrasoft.encode(data));
		if (data.flashtypeid === Terrasoft.OktellFlashType.LOST) {
			// The subscriber, while on hold, ended the call
			var callToFinish;
			if (!Ext.isEmpty(this.activeCall) && this.activeCall.state === Terrasoft.GeneralizedCallState.HOLDED) {
				callToFinish = this.activeCall;
			} else if (!Ext.isEmpty(this.consultCall) && this.consultCall.state === Terrasoft.GeneralizedCallState.HOLDED) {
				callToFinish = this.consultCall;
			}
			if (!Ext.isEmpty(callToFinish)) {
				this.fireEvent("unhold", callToFinish);
				this.updateDbCall(callToFinish, this.onUpdateDbCall);
				this.finishCall(callToFinish.id);
			}
		}
	},

	/**
  * Handles a low-level event of a user status change.
  * @private
  * @param {Object} data Event parameters.
  * @param {Boolean} data.oncallcenter The user is in the Call Center mode.
  * @param {Terrasoft.OktellAgentState} data.userstate User status.
  */
	onUserStateChanged: function (data) {
		this.fireEvent("rawMessage", "onUserStateChanged: " + Terrasoft.encode(data));
		var isCallCentreActive = data.oncallcenter;
		this.fireEvent("callCentreStateChanged", isCallCentreActive);
		if (this.userCanDial(data.userstate)) {
			this.onUserCanDial();
		}
	},

	/**
  * Handles the user's ability to make outgoing calls.
  * @private
  */
	onUserCanDial: function () {
		var canDialFeature = Terrasoft.CallFeaturesSet.CAN_DIAL;
		if (this.activeCall) {
			this.activeCall.callFeaturesSet = canDialFeature;
		}
		if (this.consultCall) {
			this.consultCall.callFeaturesSet = canDialFeature;
		}
		if (!this.activeCall && !this.consultCall) {
			this.fireEvent("lineStateChanged", { callFeaturesSet: canDialFeature });
		}
	},

	/**
  * Called when the current subscriber list is changed.
  * @param {Object[]} abonents An array of subscribers.
  */
	onAbonentsChange: function (abonents) {
		var abonentsCount = abonents.length;
		this.log("abonentsChange. Count = {0}", abonentsCount);
		var activeCall = this.activeCall;
		if (activeCall && abonentsCount === 0) {
			var holdInfo = window.oktell.getHoldInfo();
			var state = window.oktell.getState();
			if (!holdInfo.hasHold && state === Terrasoft.OktellLineState.READY) {
				var consultCall = this.consultCall;
				if (consultCall) {
					this.finishCall(consultCall.id);
				}
				this.finishCall(activeCall.id);
			}
		}
	},

	//endregion

	//region Methods: Public

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#init
  * @override
  */
	init: function () {
		this.callParent(arguments);
		var callback = function () {
			this.loginMsgService(this.msgUtilServiceUrl + this.loginMethodName, {
				"LicInfoKeys": this.licInfoKeys,
				"UserUId": Terrasoft.SysValue.CURRENT_USER.value
			}, this.connect.bind(this));
		}.bind(this);
		var configuration = Terrasoft.configuration;
		if (configuration && configuration.RootSchemaDescriptors && configuration.RootSchemaDescriptors.OktellModule) {
			require(["OktellModule"], function () {
				callback();
			});
		} else {
			callback();
		}
	},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#reConnect
  */
	reConnect: function () {
		this.unsubscribeFromOktellEvents();
		this.connect(this.initialConfig.connectionConfig);
	},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#closeConnection
  */
	closeConnection: function () {
		window.oktell.disconnect();
	},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#makeCall
  */
	makeCall: function (targetAddress) {
		var self = this;
		window.oktell.call(targetAddress, "user", function (data) {
			if (data.result) {
				return;
			}
			/*
   data.errorCode	data.errorMessage
   2001	user state - disconnected
   2002	phone not connected
   2101	user has holded call and is talking now
   2102	user in conference now
   2103	phone state not valid for call
   2104	user state - busy
   2105	calling number is talking with us
   2106	bad number
   2107	error autocall method call
   2108	error user or phone state for call
    */
			var errorInfo = {
				internalErrorCode: data.errorCode,
				data: data.errorMessage,
				source: "oktell.call",
				errorType: Terrasoft.MsgErrorType.COMMAND_ERROR
			};
			self.fireEvent("error", errorInfo);
		});
	},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#answerCall
  */
	answerCall: function () {
		var self = this;
		window.oktell.answer(function (data) {
			if (data.result) {
				return;
			}
			/*
   data.errorCode	errorMessage
   2901	incorrect state
   2902	phone probably does not support intercom calls
   */
			if (data.errorCode === 2902) {
				self.isSipAutoAnswerHeaderSupported = false;
			}
			var errorInfo = {
				internalErrorCode: data.errorCode,
				data: data.errorMessage,
				source: "oktell.answer",
				errorType: Terrasoft.MsgErrorType.COMMAND_ERROR
			};
			self.fireEvent("error", errorInfo);
		});
	},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#holdCall
  */
	holdCall: function () {
		window.oktell.hold();
	},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#dropCall
  */
	dropCall: function (call) {
		var number = call.direction === Terrasoft.CallDirection.OUT ? call.calledId : call.callerId;
		window.oktell.endCall(number);
	},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#makeConsultCall
  */
	makeConsultCall: function (call, targetAddress) {
		window.oktell.call(targetAddress);
	},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#transferCall
  */
	transferCall: function () {
		window.oktell.endCall();
	},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#cancelTransfer
  */
	cancelTransfer: function (currentCall, consultCall) {
		this.dropCall(consultCall);
	},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#blindTransferCall
  */
	blindTransferCall: function (call, targetAddress) {
		var self = this;
		window.oktell.transfer(targetAddress, function (data) {
			if (data.result) {
				return;
			}
			/*
   data.errorCode	data.errorMessage
   2301	error transfer method call
   2302	bad user or phone state for transfer
   2303	nothing to transfer
   2304	bad number for transfer
   */
			var errorInfo = {
				internalErrorCode: data.errorCode,
				data: data.errorMessage,
				source: "oktell.blindTransferCall",
				errorType: Terrasoft.MsgErrorType.COMMAND_ERROR
			};
			self.fireEvent("error", errorInfo);
		});
	},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#setUserState
  */
	setUserState: function (code, reason, callback) {
		window.oktell.setStatus(code, false, reason);
		if (Ext.isFunction(callback)) {
			callback.call(this);
		}
	},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#setWrapUpUserState
  */
	setWrapUpUserState: function (isWrapUpActive, callback) {
		var userState = isWrapUpActive ? Terrasoft.OktellAgentState.BUSY : Terrasoft.OktellAgentState.READY;
		if (Ext.isFunction(callback)) {
			var nativeEventCallback = function () {
				window.oktell.offNativeEvent("userstatechanged", nativeEventCallback);
				callback.call(this);
			}.bind(this);
			window.oktell.onNativeEvent("userstatechanged", nativeEventCallback);
		}
		window.oktell.exec("setuserstate", { userstateid: userState });
	},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#queryUserState
  */
	queryUserState: function () {
		var agentState = window.oktell.getStatus();
		this.fireEvent("agentStateChanged", { userState: agentState });
	},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#sendDtmf
  */
	sendDtmf: function (call, digit) {
		window.oktell.dtmf(digit);
	},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#queryActiveCallSnapshot
  */
	queryActiveCallSnapshot: function () {},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#queryLineState
  */
	queryLineState: function () {},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#changeCallCentreState
  */
	changeCallCentreState: function (isActive) {
		var oktellCommand = isActive ? "entercallcenter" : "exitcallcenter";
		window.oktell.exec(oktellCommand);
	},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#getCapabilities
  */
	getCapabilities: function () {
		/*jshint bitwise:false */
		var callCapabilities = Terrasoft.CallFeaturesSet.CAN_RECALL | Terrasoft.CallFeaturesSet.CAN_DIAL | Terrasoft.CallFeaturesSet.CAN_DROP | Terrasoft.CallFeaturesSet.CAN_HOLD | Terrasoft.CallFeaturesSet.CAN_UNHOLD | Terrasoft.CallFeaturesSet.CAN_COMPLETE_TRANSFER | Terrasoft.CallFeaturesSet.CAN_BLIND_TRANSFER | Terrasoft.CallFeaturesSet.CAN_MAKE_CONSULT_CALL | Terrasoft.CallFeaturesSet.CAN_DTMF;
		if (this.isSipAutoAnswerHeaderSupported) {
			callCapabilities |= Terrasoft.CallFeaturesSet.CAN_ANSWER;
		}
		var agentCapabilities = Terrasoft.AgentFeaturesSet.CAN_WRAP_UP | Terrasoft.AgentFeaturesSet.HAS_CALL_CENTRE_MODE | Terrasoft.AgentFeaturesSet.CAN_GET_CALL_RECORDS;
		/*jshint bitwise:true */
		return {
			callCapabilities: callCapabilities,
			agentCapabilities: agentCapabilities
		};
	},

	/**
  * @return {String} Gets the Oktell log as a string with a complete JSON for the message webserver.
  */
	getLog: function () {
		return window.oktell.getLog();
	},

	/**
  * Requests the addresses of the call reference records of the call.
  * @param {String} callId Call id.
  * @param {Function} callback A function that will be called when a response is received from the Oktell server. At the entrance
  * gets an array of links to the conversation records.
  * @param {Object} scope The context in which the callback function will be called.
  */
	getRecordLinks: function (callId, callback, scope) {
		var chainId = this.extractChainId(callId);
		if (Ext.isEmpty(chainId)) {
			return;
		}
		var self = this;
		window.oktell.exec("getpbxcalljournal", {
			"filter": { "idchain": chainId }
		}, function (getPbxCallJournalResult) {
			if (!getPbxCallJournalResult.result) {
				callback.call(scope || self, []);
				return;
			}
			var url = self.getOktellServerHttpUrl();
			var recordLinks = [];
			this.getOktellServerHttpTempPassword(function (tempPass) {
				Terrasoft.each(getPbxCallJournalResult.data, function (callInfo) {
					var recordLink = callInfo.recordlink;
					if (Ext.isEmpty(recordLink)) {
						return;
					}
					var recordUrl = Ext.String.format("{0}{1}?temppass={2}", url, recordLink, tempPass);
					recordLinks.push(recordUrl);
				}, self);
				callback.call(scope || self, recordLinks);
			}.bind(this));
		}.bind(this));
	},

	/**
  * @inheritdoc Terrasoft.BaseCtiProvider#queryCallRecords
  */
	queryCallRecords: function (callId, callback) {
		this.getRecordLinks(callId, callback, this);
	}

	/*jshint bitwise:true */
	//endregion

});

//endregion