/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
/**
 * Cordova WebSocket Plugin for Android
 * @author KNOWLEDGECODE <knowledgecode@gmail.com>
 * @version 0.12.0
 */
(function (window) {
    'use strict';
    var BuiltinWebSocket = window.WebSocket,
        exec = require('cordova/exec'),
        identifier = 0,
        listeners = {},
        taskQueue = {
            uuid: require('cordova/utils').createUUID(),
            tasks: [],
            push: function (fn) {
                this.tasks.push(fn);
                window.postMessage(this.uuid, '*');
            },
            listener: function (event) {
                if (event.source === window && event.data === taskQueue.uuid) {
                    event.stopPropagation();
                    if (taskQueue.tasks.length) {
                        taskQueue.tasks.shift()();
                    }
                }
            }
        },
        createMessage = function (type, data, origin) {
            var evt = document.createEvent('Event');

            evt.initEvent(type, false, false);
            switch (type) {
            case 'message':
                evt.data = data;
                evt.origin = origin;
                break;
            case 'close':
                evt.wasClean = data.substring(0, 1) === '1';
                evt.code = parseInt(data.substring(1, 5), 10) || 0;
                evt.reason = data.substring(5);
                break;
            }
            return evt;
        },
        Blob = (function () {
            if (typeof window.WebKitBlobBuilder === 'function') {
                return function (data) {
                    var blob = new window.WebKitBlobBuilder();
                    blob.append(data[0]);
                    return blob.getBlob();
                };
            }
            return window.Blob;
        }()),
        binaryToString = function (data, onComplete) {
            var blob, r;

            if (data instanceof window.ArrayBuffer || data.buffer instanceof window.ArrayBuffer) {
                blob = new Blob([data.buffer || data]);
            } else if (data instanceof window.Blob) {
                blob = data;
            } else {
                throw new TypeError('\'%s\' is not a valid value for binaryType.'.replace('%s', typeof data));
            }
            r = new window.FileReader();
            r.onload = function () {
                onComplete(this.result);
            };
            r.readAsDataURL(blob);
        },
        stringToBinary = function (data, size, binaryType) {
            var i, len, array;

            array = new window.Uint8Array(size);
            for (i = 0, len = data.length; i < len; i++) {
                array[i] = data.charCodeAt(i);
            }
            if (binaryType === 'arraybuffer') {
                return array.buffer;
            }
            if (binaryType === 'blob') {
                return new Blob([array.buffer]);
            }
            throw new TypeError('\'%s\' is not a valid value for binaryType.'.replace('%s', binaryType));
        },
        EventTarget = function () {
            this.addEventListener = function (type, listener) {
                var el = listeners[this.__getId__()][type] || [];

                if (el.indexOf(listener) < 0) {
                    el.push(listener);
                    listeners[this.__getId__()][type] = el;
                }
            };
            this.removeEventListener = function (type, listener) {
                var i, el = listeners[this.__getId__()][type] || [];

                i = el.indexOf(listener);
                if (i >= 0) {
                    el.splice(i, 1);
                }
            };
            this.dispatchEvent = function (evt) {
                var i, len, el = listeners[this.__getId__()][evt.type] || [];

                for (i = 0, len = el.length; i < len; i++) {
                    el[i].call(this, evt);
                }
            };
        },
        WebSocketPrototype = function () {
            this.CONNECTING = 0;
            this.OPEN = 1;
            this.CLOSING = 2;
            this.CLOSED = 3;
            this.send = function (data) {
                var that = this;

                if (typeof data === 'string') {
                    exec(null, null, 'WebSocket', 'send', [that.__getId__() + '0' + data]);
                } else {
                    binaryToString(data, function (blob) {
                        exec(null, null, 'WebSocket', 'send', [that.__getId__() + '1' + blob]);
                    });
                }
            };
            this.close = function (code, reason) {
                if (this.readyState === this.CONNECTING || this.readyState === this.OPEN) {
                    this.readyState = this.CLOSING;
                    exec(null, null, 'WebSocket', 'close', [this.__getId__(), code || 0, reason || '']);
                }
            };
        },
        WebSocket = function (url, protocols) {
            var i, len, that = this, id = ('0000000' + identifier.toString(16)).slice(-8);

            if (this === window) {
                throw new TypeError('Failed to construct \'WebSocket\': ' +
                    'Please use the \'new\' operator, ' +
                    'this DOM object constructor cannot be called as a function.');
            }
            if (!WebSocket.pluginOptions.override && BuiltinWebSocket) {
                return new (BuiltinWebSocket.bind.apply(BuiltinWebSocket, [].concat(this, Array.prototype.slice.call(arguments))))();
            }
            switch (arguments.length) {
            case 0:
                throw new TypeError('Failed to construct \'WebSocket\': 1 argument required, but only 0 present.');
            case 1:
                protocols = '';
                break;
            case 2:
                if (!Array.isArray(protocols)) {
                    protocols = [protocols];
                }
                for (i = 0, len = protocols.length; i < len; i++) {
                    if (!/^[0-9A-Za-z!#\$%&'\*\+\-\.\^_`|~]+$/.test(protocols[i])) {
                        throw new SyntaxError('Failed to construct \'WebSocket\': The subprotocol \'' + protocols[i] + '\' is invalid.');
                    }
                }
                protocols = len ? protocols.join(', ') : '';
                break;
            default:
                throw new TypeError('Failed to construct \'WebSocket\': No matching constructor signature.');
            }

            this.url = url;
            this.binaryType = Blob ? 'blob' : 'arraybuffer';
            this.readyState = 0;
            this.bufferedAmount = 0;
            this.onopen = null;
            this.onmessage = null;
            this.onerror = null;
            this.onclose = null;
            this.extensions = '';
            this.protocol = '';
            this.__getId__ = function () {
                return id;
            };
            listeners[id] = {};

            exec(function (data) {
                switch (data[0]) {
                case 'O':
                    taskQueue.push(function () {
                        var evt = createMessage('open'),
                            param = JSON.parse(data.substring(1));

                        that.protocol = param[0];
                        that.extensions = param[1];
                        that.readyState = that.OPEN;
                        if (that.onopen) {
                            that.onopen(evt);
                        }
                        that.dispatchEvent(evt);
                    });
                    break;
                case 'T':
                    taskQueue.push(function () {
                        var evt = createMessage('message', data.substring(1), that.url);

                        if (that.onmessage) {
                            that.onmessage(evt);
                        }
                        that.dispatchEvent(evt);
                    });
                    break;
                case 'B':
                    taskQueue.push(function () {
                        var evt = createMessage('message',
                                stringToBinary(data.substring(9), parseInt(data.substring(1, 9), 16),
                                    that.binaryType), that.url);

                        if (that.onmessage) {
                            that.onmessage(evt);
                        }
                        that.dispatchEvent(evt);
                    });
                    break;
                case 'C':
                    taskQueue.push(function () {
                        var evt = createMessage('close', data.substring(1));

                        that.readyState = that.CLOSED;
                        if (that.onclose) {
                            that.onclose(evt);
                        }
                        that.dispatchEvent(evt);
                        delete listeners[that.__getId__()];
                    });
                    break;
                }
            }, function () {
                taskQueue.push(function () {
                    var evt = createMessage('error');

                    if (that.onerror) {
                        that.onerror(evt);
                    }
                    that.dispatchEvent(evt);
                });
            }, 'WebSocket', 'create', [identifier++, url, protocols, location.origin, navigator.userAgent, WebSocket.pluginOptions || {}]);
        },
        ver = /Chrome\/(\d+)/.exec(navigator.userAgent);

    WebSocketPrototype.prototype = new EventTarget();
    WebSocketPrototype.prototype.constructor = WebSocketPrototype;
    WebSocket.prototype = new WebSocketPrototype();
    WebSocket.prototype.constructor = WebSocket;
    WebSocket.pluginOptions = {};
    module.exports = WebSocket;
    window.addEventListener('message', taskQueue.listener, true);

    if (!ver || parseInt(ver[1], 10) < 30) {
        BuiltinWebSocket = undefined;
    }
}(this));
