
/**
 * @abstract
 * @constructor
**/
function AbstractRawTCPSocket() {
    /** @type {Array<function(string)>} */
    this.onReadyReadHandlers = [];
}

/**
 * @abstract
 * @return {Promise}
**/
AbstractRawTCPSocket.prototype.connect = function() {};

/**
 * @abstract
 * @param {string} msg
**/
AbstractRawTCPSocket.prototype.write = function(msg) {};

/**
 * @param {function(string)} onReadyRead
**/
AbstractRawTCPSocket.prototype.onReadyRead = function(onReadyRead) {
    this.onReadyReadHandlers.push(onReadyRead);
};

/** @param {string} msg */
AbstractRawTCPSocket.prototype.triggerOnReadyRead = function(msg) {
    this.onReadyReadHandlers.forEach(function(handler) {
        handler(msg);
    });
};

AbstractRawTCPSocket.prototype.close = function() {};

//
// This API is not available yet
// https://www.w3.org/TR/raw-sockets/
//
///**
// * @constructor
// * @extends {AbstractRawTCPSocket}
// * @param {string} address
// * @param {number} port
//**/
//function NavigatorRawTCPSocket(address, port) {
//    AbstractRawTCPSocket.apply(this);
//
//    /** @type {string} */
//    this.address = address;
//
//    /** @type {number} */
//    this.port = port;
//}
//
//NavigatorRawTCPSocket.prototype = Object.create(AbstractRawTCPSocket);
//NavigatorRawTCPSocket.prototype.constructor = NavigatorRawTCPSocket;
//
//NavigatorRawTCPSocket.prototype.checkPermission = function() {
//    return navigator.tcpPermission.requestPermission({ remoteAddress: this.address, remotePort: this.port });
//};
//
//NavigatorRawTCPSocket.prototype.connect = function() {
//    this.sock = navigator.TCPSocket(this.address, this.port)
//};
//
//NavigatorRawTCPSocket.prototype.write = function(str) {
//};
//
//NavigatorRawTCPSocket.prototype.close = function() {
//};

/**
 * @constructor
 * @extends {AbstractRawTCPSocket}
 * @param {string} address
 * @param {number} port
**/
function NativeRawTCPSocket(address, port) {
    AbstractRawTCPSocket.apply(this);

    /**
     * @const
     * @type {number}
    **/
    this.socketId = NativeRawTCPSocket.sockCount++;

    NativeRawTCPSocket.sockets[this.socketId] = this;

    /** @type {string} */
    this.address = address;

    /** @type {number} */
    this.port = port;

    /** @type {{ onSuccess: function(), onReject: function() }|null} */
    this.connectPromise;
}

NativeRawTCPSocket.prototype = Object.create(AbstractRawTCPSocket);
NativeRawTCPSocket.prototype.constructor = NativeRawTCPSocket;

/** @return {Promise} */
NativeRawTCPSocket.prototype.connect = function() {
    return new Promise(function(onSuccess, onReject) {
        this.connectPromise = {
            onSuccess: onSuccess,
            onReject: onReject
        };
        __native.TCPConnect(this.socketId, this.address, this.port);
    });
};

/** @param {boolean} success */
NativeRawTCPSocket.prototype.onConnected = function(success) {
    if (this.connectPromise) {
        if (success)
            this.connectPromise.onSuccess();
        else
            this.connectPromise.onReject();
        this.connectPromise = null;
    }
};

/** @param {string} msg */
NativeRawTCPSocket.prototype.write = function(msg) {
    __native.TCPWrite(this.socketId, msg);
};

NativeRawTCPSocket.prototype.close = function() {
    __native.TCPClose(this.socketId);
};

/** @type {number} */
NativeRawTCPSocket.sockCount = 0;
NativeRawTCPSocket.sockets = {};

NativeRawTCPSocket.onConnected = function(sockId, success) {
    NativeRawTCPSocket.sockets[sockId].onConnected(success);
};

NativeRawTCPSocket.onReadyRead = function(sockId, msg) {
    NativeRawTCPSocket.sockets[sockId].triggerOnReadyRead(msg);
};

/**
 * @param {string} address
 * @param {number} port
 * @return {AbstractRawTCPSocket|null}
**/
function createSocket(address, port) {
    if (isNative())
        return new NativeRawTCPSocket(address, port);
//    else if (navigator && "tcpPermission" in navigator)
//        return new NavigatorRawTCPSocket(address, port);
    return null; // unsupported
}

