Source: wtapi.js

/**
 * WTAPI Library
 *
 * @version 1.0.0
 */

/**
 * Checks if string is valid ip. Both ip and ip:port are accepted
 * @param str
 * @returns {boolean}
 */
function isIp(str) {
    return /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?::(\d{1,5}))?$/g.test(str);
}

/**
 * Represents a WTAPI instance
 *
 * @constructor
 * @augments Observer
 */
function WTAPI(extension, password, url) {

    console.warn('%cThis version wtapi.js is deprecated.', 'color:red; font-size: 24px')
    console.warn('%cPlease use new version https://webapi.wildix.com/v2/', 'color:red; font-size: 24px')

	var conLocation = {
			host: '',
			protocol: 'http:'
	}

	if(url.indexOf('https://') === 0){
		conLocation.protocol = 'https:';
	}

	var newUrl = url.replace(conLocation.protocol+'//', '').split('/');
	conLocation.host = newUrl[0];

    this.version = '1.0.2';
    this.server = "wildix";
    this.extension = extension;
    this.password = password;
    this.licenseType = '';
    this.resource_prefix = 'wtapi';
    this.resource_postfix = Math.round(Math.random()*10000);
    this.resource = this._makeResource(extension, password, this.resource_prefix, this.resource_postfix);
    this.jid = extension +"@"+ this.server;
    this.jid_full = this.jid +"/"+ this.resource;
    this.sid = this._makeUniqueId(extension, password, 'wtapi');
    this.recon_time = 0;
    this.recon_timeout = false;
    this.recon_timeout_min = 2;
    this.recon_timeout_max = 16;
    this.recon_timeout_skip = 360;
    this.recon_inactivity = 45;
    this.verify_time = false;
    this.verify_timeout = 1;

    this.con = false;
    this.con_id = 0;
    this.con_rid = false;
    this.con_secs = 0;
    this.con_url = url;
    this.host = conLocation.host;
    this.protocol = conLocation.protocol;
    this.con_errors = new Array();
    this.con_wait = 30;
    this.con_debuger = null; // new JSJaCConsoleLogger(99);
    this.con_props = {
        httpbase: this.con_url,
        wait: this.con_wait,
        oDbg: this.con_debuger
    }
    this.connected = false;
    this.connecting = false;

    this.handlers = {};

    // Initialize supper
    WTAPI.Observable.call(this);

    WTAPI.ajax.settings.host = this.host;
    WTAPI.ajax.settings.protocol = this.protocol;

    // Initialize plugins
    for(var i=0; i < this.plugins.length; i++) {
        var plugin = this.plugins[i];
        var instance = new plugin.cls(this);
        this[plugin.name] = instance;
    }

    // TODO: window.onbeforeunload = this.disconnect;
}


// ============================================================
// Shared classes
// ============================================================

/**
 * @constructor
 */
WTAPI.Observable = function() {
    this._listeners = {};
}

/**
 * Registers a listener to receive events.
 *
 * @alias on
 * @param event {string}
 * @param callback {function}
 */
WTAPI.Observable.prototype.addListener = function(event, callback) {
    var scope = arguments[2] || this;
    var handler = {
        callback: callback,
        scope: scope
    }
    if (typeof(this._listeners[event]) === "undefined") {
        this._listeners[event] = [handler];
    } else {
        this._listeners[event].push(handler);
    }
};

WTAPI.Observable.prototype.on = WTAPI.Observable.prototype.addListener;

/**
 * Removes a previously registered listener.
 *
 * @param event
 * @param callback
 */
WTAPI.Observable.prototype.removeListener = function(event, callback) {
    var listeners = this._listeners[event];
    if (typeof(listeners) !== "undefined") {
        var filtered = [];
        for(var i=0; i < listeners.length; i++) {
            if (listeners[i]['callback'] != callback) {
                filtered.push(listeners[i]);
            }
        }
        this._listeners[event] = filtered;
    }
};

/**
 * Broadcast event to listeners.
 *
 * @param event
 * @private
 */
WTAPI.Observable.prototype._fire = function(event) {
    var listeners = this._listeners[event];
    var args = Array.prototype.slice.call(arguments, 1, arguments.length);

    if (typeof(listeners) !== "undefined") {
        for(var i=0; i < listeners.length; i++) {
            var listener = listeners[i];
            listener.callback.apply(listener.scope, args);
        }
    }
};

// ============================================================
// Public events
// ============================================================


/**
 * Indicates that connection to PBX is established
 *
 * @event WTAPI#connected
 */

/**
 * Indicates that connection with PBX is lost
 *
 * @event WTAPI#disconnected
 */


// ============================================================
// Static functionality
// ============================================================

WTAPI.prototype = Object.create(WTAPI.Observable.prototype);

WTAPI.prototype.plugins = [];

/**
 *
 *
 * @private
 * @param {string} name Plugin name
 * @param {object} cls Plugin class (plugin prototype)
 */
WTAPI.addPlugin = function(name, cls) {
    var plugin = {
        name: name,
        cls: cls
    }
    WTAPI.prototype.plugins.push(plugin);
}

// ============================================================
// Public functionality
// ============================================================

/**
 * Connects to a Wildix XMPP server.
 *
 * Connections can be reused between connections.
 * This means that an Connection may be connected, disconnected and then connected again.
 * Listeners of the Connection will be retained accross connections.
 *
 * If a connected Connection gets disconnected abruptly then it will try to reconnect again.
 * To stop the reconnection process, use disconnect(). Once stopped you can use connect() to manually connect to the server.
 *
 * @access public
 */
WTAPI.prototype.connect = function() {
    this.con_reconnect = true;

    WTAPI.ajax({
		url: '/api/v1/personal/login?wtapiVersion=1',
		type: 'POST',
		data: {
			login: this.extension,
			password: this.password
		},
		success: this._proxy(function(data, textStatus, jqXHR){
			if(data.type == 'result' && data.result == 'OK'){
                this._getPersonalInfo()
                    .then(this._proxy(function () {
                        this._sendLogs();
                    },this))
                    ['catch'](function () {
                        console.log('Error get personal info');
                    });
			}
		}, this),
		error: this._proxy(function(jqXHR, textStatus, data){
			if(jqXHR.status != 404){
				var error = jqXHR.statusText;
				if(jqXHR && jqXHR.responseText){
		        	try {
		        		var response = JSON.parse(jqXHR.responseText);
		        		if(response['type'] && response['type'] == 'error'){
		                    error = response.reason;
		                }
					} catch (e) {
					}
		        }
				this._fire("disconnected", error);
			}else{
				// old PBX
				this._connect();
			    this._verify();
			}

		}, this)
	});
}

/**
 * @private
 */
WTAPI.prototype._sendLogs = function() {
    if (localStorage.getItem('wtapi_' + this.host)) {
        return;
    }

    this._getPbxInfo()
        .then(this._proxy(function () {
            WTAPI.ajax({
                protocol: 'https:',
                host: 'feedback.wildix.com',
                url: '/api/v1/Analytics/wtapi',
                type: 'POST',
                data: {
                    event: 'authorization',
                    data: JSON.stringify({
                        license: this.getLicenseType(),
                        userExtension: this.getExtension(),
                        userJid: this.getFullJid(),
                        pbxSerial: this.pbxSerial,
                        pbxVersion: this.pbxVersion,
                        pbxDomain: this.host,
                        wtapiVersion: this.getVersion()
                    })
                },
                success: this._proxy(function(data, textStatus, jqXHR) {
                    localStorage.setItem('wtapi_' + this.host, '1');
                }, this),
                error: function() {
                    console.log('Error sending logs');
                }
            });
        }, this))
        ['catch'](function () {
            console.log('Error get pbx config');
        });
}

/**
 * @private
 */
WTAPI.prototype._getPbxInfo = function () {
    this.pbxSerial = '';
    this.pbxVersion = '';

    return new Promise(this._proxy(function (resolve, reject) {
        WTAPI.ajax({
            url: '/api/v1/ios/config/',
            type: 'GET',
            data: {
                fields: 'pbxInfo'
            },
            success: this._proxy(function(data, textStatus, jqXHR) {
                if (data.type === 'result' && data.result) {
                    this.pbxSerial = data.result.pbxInfo.serial ? data.result.pbxInfo.serial : '';
                    this.pbxVersion = data.result.pbxInfo.version;
                }

                resolve();
            }, this),
            error: function() {
                reject();
            }
        });
    }, this));
}

/**
 * @private
 */
WTAPI.prototype._getPersonalInfo = function() {
    return new Promise(this._proxy(function (resolve, reject) {
        WTAPI.ajax({
            url: '/api/v1/personal/info',
            type: 'GET',
            data: {
                fields: 'extension,name,jid,password,email,login,licenseType'
            },
            success: this._proxy(function(data, textStatus, jqXHR){
                if(data.type == 'result' && data.result){
                    if(data.result['extension'] == this.extension
                        || data.result['name'] == this.extension
                        || data.result['email'] == this.extension
                        || data.result['login'] == this.extension){

                        this.licenseType = data.result['licenseType'];
                        this.extension = data.result['extension'];
                        if(data.result.hasOwnProperty('password')){
                            this.password = data.result['password'];
                        }
                        this.resource = this._makeResource(this.extension, this.password, this.resource_prefix, this.resource_postfix);
                        this.jid = this.extension +"@"+ this.server;
                        this.jid_full = this.jid +"/"+ this.resource;
                        this.sid = this._makeUniqueId(this.extension, this.password, 'wtapi');
                    }

                    if(data.result.hasOwnProperty('jid')){
                        if(this.extension == data.result.extension){
                            var jid = new JSJaCJID(data.result.jid);
                            this.server = jid.getDomain();
                            this.jid = data.result.jid;
                            this.jid_full = this.jid +"/"+ this.resource;
                            this.con_props.httpbase = ((this.protocol == 'http:')? 'ws': 'wss') + '://'+ this.host+'/xmpp/';

                            this._connect(true);
                        }else{
                            this._fire("disconnected", 'Wrong data');
                        }
                    }else{
                        this._connect();
                        this._verify();
                    }
                }

                resolve();
            }, this),
            error: this._proxy(function(jqXHR, textStatus, data){
                var error = jqXHR.statusText;
                if(jqXHR && jqXHR.responseText){
                    try {
                        var response = JSON.parse(jqXHR.responseText);
                        if(response['type'] && response['type'] == 'error'){
                            error = response.reason;
                        }
                    } catch (e) {
                    }
                }
                this._fire("disconnected", error);

                reject();
            }, this)
        });
    }, this));
}

/**
 * @private
 */
WTAPI.prototype._getXmppDomain = function () {
    return isIp(this.host) ? this.server : this.host.split(':')[0];
}

/**
 * @private
 */
WTAPI.prototype._connect = function(ws) {
	ws = ws || false;
    this.con_id++;
    if(ws){
    	this.con = new JSJaCWebSocketConnection(this.con_props);
    }else{
    	this.con = new JSJaCHttpBindingConnection(this.con_props);
    }

    this.con.registerHandler('onconnect', this._proxy(this._handleConnected, this));
    this.con.registerHandler('ondisconnect', this._proxy(this._handleDisconnected, this));
    this.con.registerHandler('onerror', this._proxy(this._handleConnectionError, this));
    this.con.registerHandler('presence', this._proxy(this._handlePresence, this));
    this.con.registerHandler('message', this._proxy(this._handleMessage, this));
    this.con.registerHandler('iq', this._proxy(this._handleIQ, this));
    this.con.registerHandler('iq', 'ping', 'urn:xmpp:ping', this._proxy(this._handlePing, this));

    var params = {
        domain: this._getXmppDomain(),
        username: this.extension,
        pass: this.password,
        resource: this.resource,
        register: false
    };

    this.connecting = true;

    try {
        this.con.connect(params);
    } catch (e) {
        console.log(e);
    }
}

/**
 * Closes the connection to Wildix XMPP server.
 *
 * @access public
 */
WTAPI.prototype.disconnect = function() {
    this.con_reconnect = false;
    this._disconnect();
}

/**
 * @private
 */
WTAPI.prototype._disconnect = function() {
    this.connected = false;

    try {
        this.con.disconnect();
    } catch(e) {
        this.con_errors.push(e);
    }
}

/**
 *
 * @private
 */
WTAPI.prototype.isNewServer = function() {
    return this.server != 'wildix';
}

/**
 *
 * @private
 * @returns {JSJaCConnection}
 */
WTAPI.prototype.getConnection = function() {
    return this.con;
}

/**
 * Returns true if currently connected to the Wildix XMPP server.
 *
 * @access public
 * @returns {boolean}
 */
WTAPI.prototype.isConnected = function() {
    return this.connected;
}

/**
 * Register a handler for specified event.
 * Following events are supported:
 *   onconnect - fires when successfully connected to XMPP server (fired on reconnect also).
 *   ondisconnect - fires when disconnected from XMPP server (fired on reconnect also).
 *   onerror - fires when error returns from XMPP server.
 *   presence - fires when presence packet are received.
 *   message - fires when message packet are received.
 *   iq - fires when iq packet are received.
 *
 * @private
 * @param event {string}
 * @param callback {function}
 * @param scope {object}
 * @param priority {int}
 */
WTAPI.prototype.registerHandler = function(event, callback, scope, priority) {
    var handler = {
        callback: callback,
        scope: scope,
        priotiry: priority
    }
    if (typeof(this.handlers[event]) === "undefined") {
        this.handlers[event] = [handler];
    } else {
        this.handlers[event].push(handler);
        this.handlers[event].sort(this._priorityComparator);
    }
};

/**
 * Unregister a previously registered handler.
 *
 * @param event {string}
 * @param callback {function}
 */
WTAPI.prototype.unregisterHandler = function(event, callback) {
    var events = this.handlers[event];
    if (typeof(events) !== "undefined") {
        var filtered = [];
        for(var i=0; i < events.length; i++) {
            if (events[i]['callback'] != callback) {
                filtered.push(events[i]);
            }
        }
        this.handlers[event] = filtered;
    }
};

/**
 * Returns extension that are used to connect to Wildix XMPP server.
 *
 * @returns {string}
 */
WTAPI.prototype.getExtension = function() {
    return this.extension;
}

/**
 * Returns a JID that are used to connect to Wildix XMPP server.
 *
 * @returns {string}
 */
WTAPI.prototype.getJid = function() {
    return this.jid;
}

/**
 * Returns a full JID (with resource) that are used to connect to Wildix XMPP Server.
 *
 * @returns {string}
 */
WTAPI.prototype.getFullJid = function() {
    return this.jid_full;
}

/**
 * Returns a license type that are used to connect to Wildix XMPP Server.
 *
 * @returns {string}
 */
WTAPI.prototype.getLicenseType = function() {
    return this.licenseType;
}

/**
 * Return the version wtapi.js.
 *
 * @returns {string}
 */
WTAPI.prototype.getVersion = function() {
    return this.version;
}

/**
 * @private
 * @param event
 * @param args
 */
WTAPI.prototype._execute = function(event, args) {
    var handlers = this.handlers[event];
    if (typeof(handlers) !== "undefined") {
        for(var i=0; i < handlers.length; i++) {
            var handler = handlers[i];
            var handled = handler.callback.apply(handler.scope, args);
            if (handled) {
                break;
            }
        }
    }
}

// ============================================================
// JsJac callbacks
// ============================================================

/**
 * @private
 * @event WTAPI#connected
 */
WTAPI.prototype._handleConnected = function() {
    this.connecting = false;
    this.connected = true;
    this._fire("connected");

    // We should clear reconnection timers after good connection delay
    var id = this.con_id;
    setTimeout(function() {
        if (id == this.con_id) {
            this.recon_time = false;
            this.recon_timeout = false;
        }
    }, this.recon_timeout_skip * 1000);

    // -----
    var presence = new JSJaCPresence();
    this.con.send(presence);
}

/**
 * Connection error handler
 *
 * @private
 * @param error {Node}
 */
WTAPI.prototype._handleConnectionError = function(error) {
    // Verify whether problem is in resource name.
    if (error.getAttribute("code") == "503" && error.firstChild && error.firstChild.tagName == "remote-stream-error") {
        this.resource_postfix = Math.round(Math.random()*10000);
        this.resource = this._makeResource(this.extension, this.password, this.resource_prefix, this.resource_postfix);
        this.jid_full = this.jid +"/"+ this.resource;
    }

    if (!this._connected) {
        this.con_errors.push(error);
        this._disconnect();
        this._handleDisconnected();
    }

    this.con_errors.push(error);
    this.connecting = false;
}

/**
 * Fires when user has been disconnected (fired by jsjac connection object).
 *
 * @private
 * @event WTAPI#connected
 */
WTAPI.prototype._handleDisconnected = function() {
    var error;
    if (this.con_errors.length > 0) {
        error = this.con_errors[0];
        if (error instanceof Node) {
            var tmp = document.createElement("div");
            tmp.appendChild(this.con_errors[0]);
            error = tmp.innerHTML;
        }
    }

    this.connected = false;
    this.connecting = false;
    this._fire("disconnected", error);
}

/**
 * @private
 */
WTAPI.prototype._handlePresence = function() {
    this._execute("presence", arguments);
}

/**
 * @private
 */
WTAPI.prototype._handleMessage = function() {
    this._execute("message", arguments);
}

/**
 * @private
 */
WTAPI.prototype._handleIQ = function() {
    this._execute("iq", arguments);
}
/**
 * @private
 */
WTAPI.prototype._handlePing = function(iq) {
	var packet = new JSJaCIQ();
	packet.setTo(iq.getFrom());
	packet.setType('result');
	packet.setID(iq.getID());
    this.con.send(packet);
}


// ============================================================
// Private functionality
// ============================================================

/**
 * @private
 */
WTAPI.prototype._verify = function() {
    var time = Math.round(new Date().getTime() / 1000);

    if (this.con_reconnect) {
        if (this.con_errors.length > 0) {
            this.con_errors = new Array();

            // Define reconnection timeout
            this.recon_time = this.verify_timeout;
            this.recon_timeout = this._random(this.recon_timeout_min, this.recon_timeout_max);
        } else if (this.connected == false && this.connecting == false) {
            if (this.recon_time) {
                // We should connect to server when timeout has come
                if (this.recon_time >= this.recon_timeout) {
                    this._connect();
                } else {
                    this.recon_time += this.verify_timeout;
                }
            } else {
                this._connect();
            }
        } else if (this.connected == true) {
            this.con_secs += this.verify_timeout;

            // Solution to detect if user hasn't internet connection.
            if (this.con_rid != this.con._rid) {
                this.con_rid = this.con._rid;
                this.con_secs = 0;
            } else if (this.con_secs > this.recon_inactivity) {
                if (this.connected && !this.connecting) {
                    this.con_errors.push("Found this connection is not alive!");
                    this._disconnect();
                }
            }

            // Solution to check if computer was in sleep mode or other problem occurs (browser doesn't execute javascript)
            if (this.verify_time) {
                var diff = Math.abs(time - this.verify_time);

                if (diff > this.con_wait) {
                    if (!this.connecting) {
                        this.con_errors.push("Found this computer was in sleep mode");
                        this._disconnect();
                    }
                }
            }
        }
    }

    this.verify_time = time;
    setTimeout(this._proxy(this._verify, this), this.verify_timeout * 1000);
}

/**
 * Generates a resource for connection.
 * Resource generation is based on username, password, browser name and protocol.
 *
 * @private
 * @param username
 * @param password
 * @param prefix
 * @param postfix
 */
WTAPI.prototype._makeResource = function(username, password, prefix, postfix) {
    var str = username +"-"+ password +"-"+ window.location.protocol +"-"+ navigator.userAgent;
    var hash1 = (5381<<16) + 5381;
    var hash2 = hash1;
    var hashPos = 0;
    var resource = "";

    while(hashPos < str.length) {
        hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ str.charCodeAt(hashPos);
        if( hashPos == str.length - 1) {
            break;
        }
        hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ str.charCodeAt(hashPos + 1);
        hashPos += 2;
    }

    hash1 = new String(hash1);
    hash2 = new String(hash2 * 1566083941);

    resource = prefix +"-"+ username + "-" + hash1.substr(hash1.length - 4, 4) + "-" + hash2.substr(hash1.length - 4, 4)

    if (postfix) {
        resource = resource +"-"+ postfix;
    }

    return resource;
}

/**
 * Generates an unique id for current browser (used in resource)
 *
 * @private
 * @param username
 * @param password
 * @param prefix
 */
WTAPI.prototype._makeUniqueId = function(username, password, prefix) {
    var str = username + password + window.location.protocol + navigator.userAgent + Math.random();
    var hash1 = (5381<<16) + 5381;
    var hash2 = hash1;
    var hashPos = 0;

    while(hashPos < str.length) {
        hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ str.charCodeAt(hashPos);
        if( hashPos == str.length - 1) {
            break;
        }
        hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ str.charCodeAt(hashPos + 1);
        hashPos += 2;
    }

    hash1 = new String(hash1);
    hash2 = new String(hash2 * 1566083941);

    return prefix +"_"+ username + "_" + hash1.substr(hash1.length - 4, 4) + "_" + hash2.substr(hash1.length - 4, 4);
}

/**
 * Generates a random number in specified range.
 *
 * @private
 * @param from int
 * @param to int
 */
WTAPI.prototype._random = function(from, to) {
   return Math.floor(Math.random() * (to - from + 1) + from);
}

/**
 * Wraps callback to be sure that function will be executed in proper scope
 *
 * @private
 * @param callback function
 * @param scope object
 */
WTAPI.prototype._proxy = function(callback, scope) {
   return function() {
       callback.apply(scope, arguments);
   };
}

/**
 * TODO: Description
 *
 * @private
 */
WTAPI.prototype._priorityComparator = function(a, b) {
    if (a.priotiry < b.priotiry)
        return -1;
    if (a.priotiry > b.priotiry)
        return 1;
    return 0;
}


// ============================================================
// Public classes
// ============================================================

/**
 * Represents a user that are participated in Wildix PBX.
 *
 * @param jid {string}
 * @param name {string}
 * @constructor
 */
WTAPI.User = function(jid, name) {
    this._id = WTAPI.User._id++;
    this._jid = jid;
    this._name = name || null;
    this._group = null;

    // Determine extension
    var s = this._jid.split("@");
    if(s.length > 1 && (s[1] == '172.16.0.6' || s[1] == 'kite.wildix.com')){
    	this._group = 'Kite';
    }
    this._extension = s[0];
}

WTAPI.User._id = 1;

/**
 * Returns a User <b>temporary</b> id.
 * This identifier is not unique and will be changed between page refresh
 *
 * @returns {string}
 */
WTAPI.User.prototype.getId = function() {
    return this._jid;
}

/**
 * Returns a JID of User.
 * JID is a unique identity of a user.
 *
 * @returns {string}
 */
WTAPI.User.prototype.getJid = function() {
    return this._jid;
}

/**
 * Returns a name of User. Can be null when name is not available at that moment.
 * For example it is not possible to know a name of users that are not in roster.
 * If you want to use remote user name, you should request roster before ( {@link https://www.wildix.com/webapi/doc/WTAPI.RosterPlugin.html | WTAPI.RosterPlugin().getRoster(callback)} ).
 *
 * @returns {string|null}
 */
WTAPI.User.prototype.getName = function() {
    return this._name;
}

WTAPI.User.prototype.setName = function(name) {
    this._name = name;
}

/**
 * Returns an extension of User. Can be null when user doesn't have extension, for example user from MashUp service.
 *
 * @returns {string|null}
 */
WTAPI.User.prototype.getExtension = function() {
    return this._extension;
}

WTAPI.User.prototype.isGroupExist = function() {
    return this._group !== null;
}

WTAPI.User.prototype.getGroup = function() {
    return this._group;
}

WTAPI.User.prototype.setGroup = function(group) {
    this._group = group;
}

/**
 * Represents call information of user.
 * Used in user presence status and can be received from WTAPI.Presence.
 *
 * @param name {string}
 * @param number {string}
 * @constructor
 */
WTAPI.ConnectedCall = function(name, number) {
    this._name = name;
    this._number = number;
}

/**
 * Returns the name of this connected call.
 *
 * @returns {*}
 */
WTAPI.ConnectedCall.prototype.getName = function() {
    return this._name;
}

/**
 * Returns the number of this connected call.
 *
 * @returns {*}
 */
WTAPI.ConnectedCall.prototype.getNumber = function() {
    return this._number;
}

/**
 * Represents a geographic location of User.
 * Used in user presence and can be received from WTAPI.Presence.
 *
 * @param address {string}
 * @param lat {string}
 * @param lng {string}
 * @constructor
 */
WTAPI.Location = function(address, lat, lng) {
    this._address = address;
    this._lat = lat;
    this._lng = lng;
}

/**
 * Returns the human-readable address of this location.
 *
 * @returns {*}
 */
WTAPI.Location.prototype.getAddress = function() {
    return this._address;
}

/**
 * Returns the latitude in degrees.
 *
 * @returns {*}
 */
WTAPI.Location.prototype.getLat = function() {
    return this._lat;
}

/**
 * Returns the longitude in degrees.
 *
 * @returns {*}
 */
WTAPI.Location.prototype.getLng = function() {
    return this._lng;
}

/**
 * Represents a profile data location of User.
 * Used in user presence and can be received from WTAPI.Presence.
 *
 * @param mail {string}
 * @param authProvider {string}
 * @param url {string}
 * @param gender {string}
 * @param language {string}
 * @param parentHostUrl {string}
 * @param photo {string}
 * @constructor
 */
WTAPI.ProfileData = function(mail, authProvider, url, gender, language, parentHostUrl, photo) {
    this._mail = mail;
    this._authProvider = authProvider;
    this._url = url;
    this._gender = gender;
    this._language = language;
    this._parentHostUrl = parentHostUrl;
    this._photo = photo;
}

/**
 * Returns the mail address of this profile data.
 *
 * @returns {*}
 */
WTAPI.ProfileData.prototype.getMail = function() {
    return this._mail;
}

/**
 * Returns the auth provider.
 *
 * @returns {*}
 */
WTAPI.ProfileData.prototype.getAuthProvider = function() {
    return this._authProvider;
}

/**
 * Returns the url.
 *
 * @returns {*}
 */
WTAPI.ProfileData.prototype.getUrl = function() {
    return this._url;
}

/**
 * Returns the gender.
 *
 * @returns {*}
 */
WTAPI.ProfileData.prototype.getGender = function() {
    return this._gender;
}

/**
 * Returns the language.
 *
 * @returns {*}
 */
WTAPI.ProfileData.prototype.getLanguage = function() {
    return this._language;
}

/**
 * Returns the parent host url.
 *
 * @returns {*}
 */
WTAPI.ProfileData.prototype.getParentHostUrl = function() {
    return this._parentHostUrl;
}

/**
 * Returns the photo url.
 *
 * @returns {*}
 */
WTAPI.ProfileData.prototype.getPhoto = function() {
    return this._photo;
}


/**
 * Represents a Presence of a User.
 * Use WTAPI.Presence.Builder to create a new presence.
 *
 * @memberof WTAPI
 * @alias WTAPI.Presence
 * @public
 * @constructor
 */
WTAPI.Presence = function() {
    this._online = false;
    this._show = WTAPI.Presence.NONE;
    this._deviceShow = WTAPI.Presence.NONE;
    this._status = null;
    this._location = null;
    this._profileData = null;
    this._until = null;
    this._connectedCall = null;
}

WTAPI.Presence.NONE = 0;
WTAPI.Presence.AWAY = 1;
WTAPI.Presence.DND = 2;
WTAPI.Presence.RINGING = 3;
WTAPI.Presence.TALKING = 4;
WTAPI.Presence.RT = 5;
WTAPI.Presence.MUR = 6;
WTAPI.Presence.REGISTERED = 7;

/**
 * Returns true if user is online (available) and false if the user is offline (unavailable).
 *
 * @returns {boolean}
 */
WTAPI.Presence.prototype.isOnline = function() {
    return this._online;
}

/**
 * Returns true if user set away status.
 *
 * @returns {boolean}
 */
WTAPI.Presence.prototype.isAway = function() {
    return this._show === WTAPI.Presence.AWAY;
}

/**
 * Returns true if user set DND status.
 *
 * @returns {boolean}
 */
WTAPI.Presence.prototype.isDND = function() {
    return this._show === WTAPI.Presence.DND;
}

/**
 * Returns true if user set MUR status.
 *
 * @returns {boolean}
 */
WTAPI.Presence.prototype.isMUR = function() {
    return this._show === WTAPI.Presence.MUR;
}

/**
 * Returns true if somebody is calling to user.
 *
 * @returns {boolean}
 */
WTAPI.Presence.prototype.isRinging = function() {
    return (this._deviceShow === WTAPI.Presence.RINGING || this._deviceShow === WTAPI.Presence.RT);
}


/**
 * Returns true if user is talking with somebody.
 *
 * @returns {boolean}
 */
WTAPI.Presence.prototype.isTalking = function() {
    return (this._deviceShow === WTAPI.Presence.TALKING || this._deviceShow === WTAPI.Presence.RT);
}

/**
 * Returns true if user is talking and somebody tries to call him.
 *
 * @returns {boolean}
 */
WTAPI.Presence.prototype.isTalkingAndRinging = function() {
    return this._deviceShow === WTAPI.Presence.RT;
}

/**
 * Returns true if user has a registered device.
 *
 * @returns {boolean}
 */
WTAPI.Presence.prototype.isRegisteredDevice = function() {
    return this._deviceShow === WTAPI.Presence.REGISTERED;
}

/**
 * Returns true if user is offline and has default values for other properties.
 *
 * @returns {boolean}
 */
WTAPI.Presence.prototype.isEmpty = function() {
    if (this._show !== WTAPI.Presence.NONE ||
        this._deviceShow !== WTAPI.Presence.NONE ||
        this._location !== null ||
        this._profileData !== null ||
        this._until !== null ||
        this._status !== null) {
        return false;
    }

    return true;
}

/**
 * Returns a status icon representing a current status.
 * Possible values:
 *  away
 *  away-offline
 *  dnd
 *  dnd-offline
 *  mur
 *  mur-offline
 *  online
 *  offline
 *
 * @returns {String}
 */
WTAPI.Presence.prototype.getStatusIcon = function() {
    switch (this._show) {
        case WTAPI.Presence.AWAY:
            return this._online ? "away" : "away-offline"
        case WTAPI.Presence.DND:
            return this._online ? "dnd" : "dnd-offline"
        case WTAPI.Presence.MUR:
            return this._online ? "mur" : "mur-offline"
        default:
            return this._online ? "online" : "offline"
    }
}

/**
 * Returns the status message of the presence, or null if user doesn't set it.
 *
 * @returns {String|null}
 */
WTAPI.Presence.prototype.getStatusMessage = function() {
    return this._status;
}

/**
 * Returns true if user set custom status message.
 *
 * @returns {boolean}
 */
WTAPI.Presence.prototype.isStatusMessageAvailable = function() {
    return this._status !== null;
}

/**
 * Returns the expire date of the presence, or null if user doesn't set it.
 * Expire date - a date when this presence are valid.
 *
 * @returns {Date|null}
 */
WTAPI.Presence.prototype.getStatusUntil = function() {
    return this._until;
}

/**
 * Returns true if user set expire date for presence.
 *
 * @returns {boolean}
 */
WTAPI.Presence.prototype.isStatusUntilAvailable = function() {
    return this._until !== null;
}

/**
 * Returns call information in presence status of user, or null if there is no active call.
 *
 * @returns {WTAPI.ConnectedCall|null}
 */
WTAPI.Presence.prototype.getConnectedCall = function() {
	return this._connectedCall;
}

/**
 * Returns true if user talking on the phone.
 *
 * @returns {boolean}
 */
WTAPI.Presence.prototype.isConnectedCallAvailable = function() {
    return this._connectedCall instanceof WTAPI.ConnectedCall;
}

/**
 * Returns location of user, or null if user doesn't set it.
 *
 * @returns {WTAPI.Location|null}
 */
WTAPI.Presence.prototype.getLocation = function() {
    return this._location;
}

/**
 * Returns profile data of user, or null if user doesn't set it.
 *
 * @returns {WTAPI.ProfileData|null}
 */
WTAPI.Presence.prototype.getProfileData = function() {
    return this._profileData;
}

/**
 * Returns true if user set his location.
 *
 * @returns {boolean}
 */
WTAPI.Presence.prototype.isLocationAvailable = function() {
    return this._location instanceof WTAPI.Location;
}

/**
 * Constructor for Presence builder.
 *
 * @example
 * var untilDate = new Date((new Date()).getTime() + 1*60*60*1000); //current date +1 hour
 * var builder = new WTAPI.Presence.Builder();
 * builder.setAway();
 * builder.setStatusMessage(message);
 * builder.setStatusUntil(untilDate);
 * var presence = builder.build();
 * wtapi.presence.changePersonalPresence(presence, onChangePresenceCallback);
 *
 * @returns {WTAPI.Presence.Builder}
 * @constructor
 */
WTAPI.Presence.Builder = function() {
    // Set status
    var presence = new WTAPI.Presence();
    this._getPresence = function(){
    	return presence;
    }
}
/**
 * Set online (available) status.
 *
 * @returns {WTAPI.Presence.Builder}
 */
WTAPI.Presence.Builder.prototype.setOnline = function() {
	this._getPresence()._online = true;
    return this;
}


/**
 * Set away status.
 *
 * @returns {WTAPI.Presence.Builder}
 */
WTAPI.Presence.Builder.prototype.setAway = function() {
	this._getPresence()._show = WTAPI.Presence.AWAY;
    return this;
}

/**
 * Set dnd status.
 *
 * @returns {WTAPI.Presence.Builder}
 */
WTAPI.Presence.Builder.prototype.setDND = function() {
	this._getPresence()._show = WTAPI.Presence.DND;
    return this;
}

/**
 * Set mur status.
 *
 * @returns {WTAPI.Presence.Builder}
 */
WTAPI.Presence.Builder.prototype.setMUR = function() {
	this._getPresence()._show = WTAPI.Presence.MUR;
    return this;
}

/**
 * Set ringing status.
 *
 * @returns {WTAPI.Presence.Builder}
 * @private
 */
WTAPI.Presence.Builder.prototype.setRinging = function() {
	this._getPresence()._deviceShow = WTAPI.Presence.RINGING;
    return this;
}

/**
 * Set talking status.
 *
 * @returns {WTAPI.Presence.Builder}
 * @private
 */
WTAPI.Presence.Builder.prototype.setTalking = function() {
	this._getPresence()._deviceShow = WTAPI.Presence.TALKING;
    return this;
}

/**
 * Set ringing and talking status.
 *
 * @returns {WTAPI.Presence.Builder}
 * @private
 */
WTAPI.Presence.Builder.prototype.setRingingAndTalking = function() {
	this._getPresence()._deviceShow = WTAPI.Presence.RT;
    return this;
}

/**
 * Set registered device.
 *
 * @returns {WTAPI.Presence.Builder}
 * @private
 */
WTAPI.Presence.Builder.prototype.setRegisteredDevice = function() {
	this._getPresence()._deviceShow = WTAPI.Presence.REGISTERED;
    return this;
}

/**
 * Set message.
 *
 * @param message {string}
 * @returns {WTAPI.Presence.Builder}
 */
WTAPI.Presence.Builder.prototype.setStatusMessage = function(message) {
	this._getPresence()._status = message;
    return this;
}

/**
 * Set until.
 *
 * @param date {Date}
 * @returns {WTAPI.Presence.Builder}
 */
WTAPI.Presence.Builder.prototype.setStatusUntil = function(date) {
	this._getPresence()._until = date;
    return this;
}

/**
 * Set location.
 *
 * @param location {WTAPI.Location}
 * @returns {WTAPI.Presence.Builder}
 */
WTAPI.Presence.Builder.prototype.setLocation = function(location) {
    if (!(location instanceof WTAPI.Location)) {
        throw new Error("Invalid location argument, it should be a WTAPI.Location instance");
    }
    this._getPresence()._location = location;
    return this;
}

/**
 * Set connected call information.
 *
 * @param call {WTAPI.ConnectedCall}
 * @returns {WTAPI.Presence.Builder}
 */
WTAPI.Presence.Builder.prototype.setConnectedCall = function(call) {
    if (!(call instanceof WTAPI.ConnectedCall)) {
        throw new Error("Invalid call argument, it should be a WTAPI.ConnectedCall instance");
    }
    this._getPresence()._connectedCall = call;
    return this;
}

/**
 * Set profile data.
 *
 * @param profileData {WTAPI.ProfileData}
 * @returns {WTAPI.Presence.Builder}
 */
WTAPI.Presence.Builder.prototype.setProfileData = function(profileData) {
    if (!(profileData instanceof WTAPI.ProfileData)) {
        throw new Error("Invalid profile data argument, it should be a WTAPI.ProfileData instance");
    }
    this._getPresence()._profileData = profileData;
    return this;
}

/**
 * Build the presence object.
 *
 * @returns {WTAPI.Presence}
 */
WTAPI.Presence.Builder.prototype.build = function() {
    return this._getPresence();
}



/**
 * Represents a chat message.
 * Use WTAPI.Message.Builder to create a new message.
 *
 * @memberof WTAPI
 * @alias WTAPI.Message
 * @public
 * @constructor
 */
WTAPI.Message = function() {
    this._id = "";
    this._body = "";
}

/**
 * Returns id of the message, or empty string if id has not been set.
 *
 * @returns {string}
 */
WTAPI.Message.prototype.getId = function() {
    return this._id;
}

/**
 * Returns body of the message.
 *
 * @returns {string}
 */
WTAPI.Message.prototype.getBody = function() {
    return this._body;
}

/**
 *
 * @memberof WTAPI.Message
 * @alias WTAPI.Message.Builder
 * @public
 * @constructor
 */
WTAPI.Message.Builder = function() {
    var message = new WTAPI.Message();

    return {
        withId: function(id) {
            message._id = id;
            return this;
        },
        withAutoGeneratedId: function() {
            message._id = this._rand() + this._rand() + '-' + this._rand() + '-' + this._rand() +
                '-' + this._rand() + '-' + this._rand() + this._rand() + this._rand();
            return this;
        },
        withBody: function(body) {
            message._body = body;
            return this;
        },
        build: function() {
            return message;
        },
        _rand: function() {
          return Math.floor((1 + Math.random()) * 0x10000)
                     .toString(16)
                     .substring(1);
        }
    }
}