"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const rxjs_1 = require("rxjs");
const log_1 = tslib_1.__importDefault(require("../log"));
const websocket_adapter_1 = require("./websocket-adapter");
let factory = (path) => new websocket_adapter_1.BrowserWebSocketAdapter(path);
/**
 * Set the websocket generator. The default one is compatible
 * with browsers and nodejs.
 * @param generator A function taking a path and creating a websocket adapter instance
 */
function setFactory(generator) {
    factory = generator;
}
exports.setFactory = setFactory;
/**
 * Single peer connection to one single node.
 */
class WebSocketConnection {
    /**
     * @param addr      Absolute address of the distant peer
     * @param service   Name of the service to reach
     */
    constructor(addr, service) {
        let url;
        if (typeof addr === "string") {
            url = new URL(addr);
        }
        else {
            url = addr;
        }
        // We want any pathname to contain a "/" at the end. This is motivated
        // by the fact that URL will not allow you to have an empty pathname,
        // which will always equal to "/" if there isn't any
        if (url.pathname.slice(-1) !== "/") {
            url.pathname = url.pathname + "/";
        }
        if (url.username !== "" || url.password !== "") {
            throw new Error("addr contains authentication, which is not supported");
        }
        if (url.search !== "" || url.hash !== "") {
            throw new Error("addr contains more data than the origin");
        }
        if (typeof globalThis !== "undefined" && typeof globalThis.location !== "undefined") {
            if (globalThis.location.protocol === "https:") {
                url.protocol = "wss";
            }
        }
        this.service = service;
        this.timeout = 30 * 1000; // 30s by default
        this.url = url;
    }
    /** @inheritdoc */
    getURL() {
        // Retro compatibility: this.url always ends with a slash, but the old
        // behavior needs no trailing slash
        return this.url.href.slice(0, -1);
    }
    /** @inheritdoc */
    setTimeout(value) {
        this.timeout = value;
    }
    /** @inheritdoc */
    send(message, reply) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((complete, error) => {
                this.sendStream(message, reply).subscribe({
                    complete,
                    error,
                    next: ([m, ws]) => {
                        complete(m);
                        ws.close(1000);
                    },
                });
            });
        });
    }
    copy(service) {
        return new WebSocketConnection(this.url, service);
    }
    /**
     * @deprecated - use directly a RosterWSConnection if you want that
     * @param p
     */
    setParallel(p) {
        if (p > 1) {
            throw new Error("Single connection doesn't support more than one parallel");
        }
    }
    /** @inheritdoc */
    sendStream(message, reply) {
        if (!message.$type) {
            throw new Error(`message "${message.constructor.name}" is not registered`);
        }
        if (!reply.$type) {
            throw new Error(`message "${reply.constructor.name}" is not registered`);
        }
        return new rxjs_1.Observable((sub) => {
            const url = new URL(this.url.href);
            url.pathname += `${this.service}/${message.$type.name.replace(/.*\./, "")}`;
            log_1.default.lvl4(`Socket: new WebSocket(${url.href})`);
            const ws = factory(url.href);
            const bytes = Buffer.from(message.$type.encode(message).finish());
            const timer = setTimeout(() => ws.close(1000, "timeout"), this.timeout);
            ws.onOpen(() => {
                log_1.default.lvl3("Sending message to", url.href);
                ws.send(bytes);
            });
            ws.onMessage((data) => {
                clearTimeout(timer);
                const buf = Buffer.from(data);
                log_1.default.lvl4("Getting message with length:", buf.length);
                try {
                    const ret = reply.decode(buf);
                    sub.next([ret, ws]);
                }
                catch (err) {
                    sub.error(err);
                }
            });
            ws.onClose((code, reason) => {
                // nativescript-websocket on iOS doesn't return error-code 1002 in case of error, but sets the 'reason'
                // to non-null in case of error.
                if (code !== 1000 || (reason && reason !== "")) {
                    sub.error(new Error(reason));
                }
                else {
                    sub.complete();
                }
            });
            ws.onError((err) => {
                clearTimeout(timer);
                if (err !== undefined) {
                    sub.error(new Error(`error in websocket ${url.href}: ${err.message}`));
                }
                else {
                    sub.error(new Error("unknown reason"));
                }
            });
        });
    }
}
exports.WebSocketConnection = WebSocketConnection;
/**
 * Single peer connection that reaches only the leader of the roster
 */
class LeaderConnection extends WebSocketConnection {
    /**
     * @param roster    The roster to use
     * @param service   The name of the service
     */
    constructor(roster, service) {
        if (roster.list.length === 0) {
            throw new Error("Roster should have at least one node");
        }
        super(roster.list[0].getWebSocketAddress(), service);
    }
}
exports.LeaderConnection = LeaderConnection;
