(function (globalScope) {

    function stringToArrayBuffer(string) {
        // UTF-16LE
        var buf = new ArrayBuffer(string.length * 2);
        var bufView = new Uint16Array(buf);
        for (var i = 0, strLen = string.length; i < strLen; i++) {
            bufView[i] = string.charCodeAt(i);
        }
        return buf;
    }

    function arrayBufferToString(buf) {
        return String.fromCharCode.apply(null, new Uint16Array(buf));
    }

    function getResultCode(result) {

        if (result != null && result.resultCode != null) {
            return result.resultCode;
        }

        return result;
    }

    function closeSocket(socketId) {

        try {
            chrome.sockets.udp.close(socketId);
        } catch (err) {

        }
    }

    function findServersInternal(timeoutMs) {

        var deferred = DeferredBuilder.Deferred();

        var servers = [];

        // Expected server properties
        // Name, Id, Address, EndpointAddress (optional)

        var chrome = globalScope.chrome;

        if (!chrome) {
            deferred.resolveWith(null, [servers]);
            return deferred.promise();
        }

        var timeout;
        var socketId;

        function onTimerExpired() {
            deferred.resolveWith(null, [servers]);

            if (socketId) {
                chrome.sockets.udp.onReceive.removeListener(onReceive);
                closeSocket(socketId);
            }
        }

        function startTimer() {

            Logger.log('starting udp receive timer with timeout ms: ' + timeoutMs);

            timeout = setTimeout(onTimerExpired, timeoutMs);
        }

        function onReceive(info) {

            try {

                Logger.log('ServerDiscovery message received');

                Logger.log(info);

                if (info != null && info.socketId == socketId) {
                    var json = arrayBufferToString(info.data);
                    Logger.log('Server discovery json: ' + json);
                    var server = JSON.parse(json);

                    server.RemoteAddress = info.remoteAddress;

                    if (info.remotePort) {
                        server.RemoteAddress += ':' + info.remotePort;
                    }

                    servers.push(server);
                }

            } catch (err) {
                Logger.log('Error receiving server info: ' + err);
            }
        }

        var port = 7359;
        Logger.log('chrome.sockets.udp.create');

        startTimer();

        chrome.sockets.udp.create(function (createInfo) {

            if (!createInfo) {
                Logger.log('create fail');
                return;
            }
            if (!createInfo.socketId) {
                Logger.log('create fail');
                return;
            }

            socketId = createInfo.socketId;

            Logger.log('chrome.sockets.udp.bind');
            chrome.sockets.udp.bind(createInfo.socketId, '0.0.0.0', 0, function (bindResult) {

                if (getResultCode(bindResult) != 0) {
                    Logger.log('bind fail: ' + bindResult);
                    return;
                }

                var data = stringToArrayBuffer('who is EmbyServer?');

                Logger.log('chrome.sockets.udp.send');

                chrome.sockets.udp.send(createInfo.socketId, data, '255.255.255.255', port, function (sendResult) {

                    if (getResultCode(sendResult) != 0) {
                        Logger.log('send fail: ' + sendResult);

                    } else {
                        chrome.sockets.udp.onReceive.addListener(onReceive);
                        Logger.log('sendTo: success ' + port);
                    }
                });
            });
        });

        return deferred.promise();
    }

    globalScope.ServerDiscovery = {

        findServers: function (timeoutMs) {

            var deferred = DeferredBuilder.Deferred();

            deviceReadyPromise.done(function () {

                try {
                    findServersInternal(timeoutMs).done(function (result) {

                        deferred.resolveWith(null, [result]);

                    }).fail(function () {

                        deferred.resolveWith(null, [[]]);
                    });
                } catch (err) {
                    deferred.resolveWith(null, [[]]);
                }
            });

            return deferred.promise();
        }
    };

    var deviceReadyDeferred = DeferredBuilder.Deferred();
    var deviceReadyPromise = deviceReadyDeferred.promise();

    document.addEventListener("deviceready", function () {

        deviceReadyDeferred.resolve();

    }, false);


})(window);