"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const Peer_1 = require("../../../../../api/peer/Peer");
const _1 = require("../../../../../Class/$");
const Semifunctional_1 = require("../../../../../Class/Semifunctional");
const stringify_1 = require("../../../../../spec/stringify");
const _ids_1 = require("../../../../_ids");
class PeerTransmitter extends Semifunctional_1.Semifunctional {
    constructor(system) {
        super({
            fi: ['opt'],
            i: ['answer', 'stream', 'close'],
            o: ['offer', 'channel'],
        }, {
            input: {
                stream: {
                    ref: true,
                },
            },
            output: {
                channel: {
                    ref: true,
                },
            },
        }, system, _ids_1.ID_PEER_TRANSMITTER);
        this._peer = undefined;
        this._unlisten = undefined;
        this._connected = false;
        this._offered = false;
        this._flag_err_peer_construct = false;
        this._flag_err_answer_without_offer = false;
        this._stream = null;
        this._add_stream = (stream) => {
            // console.log('Peer', '_add_stream')
            this._stream = stream;
            if (this._peer) {
                this._peer.addStream(stream);
            }
        };
        this._remove_stream = (stream) => {
            // console.log('Transmitter', '_remove_stream')
            this._peer.removeStream();
        };
        this._output_port = () => {
            // console.log('Transmitter', '_output_port')
            const peer = this._peer;
            const channel = new (class Channel extends _1.$ {
                constructor() {
                    super(...arguments);
                    this.__ = ['MS'];
                }
                send(data) {
                    return __awaiter(this, void 0, void 0, function* () {
                        const _data = (0, stringify_1.stringify)(data);
                        peer.send(_data);
                        return;
                    });
                }
            })(this.__system);
            this._output.channel.push(channel);
        };
        this._setup_peer = () => {
            // console.log('Transmitter', '_setup_peer')
            const signal_listener = (signal) => {
                // console.log('Transmitter', 'signal', signal)
                const { sdp } = signal;
                this._output.offer.push(sdp);
            };
            const connect_listener = () => {
                // console.log('Transmitter', 'connect')
                this._connected = true;
                this._output_port();
            };
            const error_listener = (err) => {
                // console.log('Transmitter', 'error', err)
                this.err(err.message);
            };
            const close_listener = () => {
                // console.log('Transmitter', 'close')
                this._disconnect();
            };
            this._peer.addListener('connect', connect_listener);
            this._peer.addListener('error', error_listener);
            this._peer.addListener('close', close_listener);
            return () => {
                this._peer.removeListener('connect', connect_listener);
                this._peer.removeListener('error', error_listener);
                this._peer.removeListener('close', close_listener);
            };
        };
        this._disconnect = () => {
            if (!this._connected) {
                return;
            }
            const unlisten = this._unlisten;
            unlisten();
            this._unlisten = undefined;
            this._peer.close();
            this._peer = undefined;
            this._connected = false;
            this._forward_empty('offer');
            this._forward_empty('channel');
            this._done({});
        };
        this.addListener('destroy', () => {
            if (this._connected) {
                this._disconnect();
            }
        });
        this.addListener('take_err', () => {
            if (!this._backwarding) {
                if (this._flag_err_answer_without_offer) {
                    this._flag_err_answer_without_offer = false;
                    this._backward('answer');
                }
                else if (this._flag_err_peer_construct) {
                    this._flag_err_peer_construct = false;
                    this._backward('opt');
                }
                else {
                    throw new Error('PeerTransmitter: invalid err state');
                }
            }
        });
    }
    f({ opt }) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                this._peer = new Peer_1.Peer(this.__system, true, opt);
            }
            catch (err) {
                const { message } = err;
                const FAIL_TO_CONSTRUCT_MSG_START = `Failed to construct 'RTCPeerConnection': `;
                if (message.startsWith(FAIL_TO_CONSTRUCT_MSG_START)) {
                    const _err = message
                        .substr(0, message.length - 1)
                        .replace(FAIL_TO_CONSTRUCT_MSG_START, '');
                    this.err(_err);
                    return;
                }
                else {
                    this.err(err.message);
                    return;
                }
            }
            this._unlisten = this._setup_peer();
            const offer = yield this._peer.offer();
            this._offered = true;
            this._output.offer.push(offer);
        });
    }
    onRefInputData(name, unit) {
        // if (name === 'stream') {
        unit.mediaStream((_stream) => {
            if (_stream === null) {
                if (this._stream) {
                    this._remove_stream(this._stream);
                }
                this._stream = null;
            }
            else {
                if (this._stream) {
                    this._remove_stream(this._stream);
                }
                this._add_stream(_stream);
                this._stream = _stream;
            }
        });
        // }
    }
    onDataInputData(name, data) {
        return __awaiter(this, void 0, void 0, function* () {
            // console.log('Transmitter', 'onDataInputData', name, data)
            if (this.hasErr()) {
                this._backwarding = true;
                this.takeErr();
                this._backwarding = false;
            }
            if (name === 'answer') {
                const sdp = data;
                if (this._offered) {
                    try {
                        yield this._peer.acceptAnswer(sdp);
                    }
                    catch (err) {
                        this.err(err.message);
                        return;
                    }
                }
                else {
                    this._flag_err_answer_without_offer = true;
                    this.err('cannot answer without offer');
                }
            }
            else if (name === 'close') {
                this._disconnect();
                this._output.offer.pull();
                this._output.channel.pull();
                this._done({});
                this._input.close.pull();
            }
        });
    }
    onDataInputDrop(name) {
        if (name === 'answer') {
            if (this._flag_err_answer_without_offer) {
                this.takeErr();
            }
            if (this._connected) {
                this._disconnect();
            }
        }
    }
    onDataOutputDrop(name) {
        // console.log('PeerTransmitter', 'onDataOutputDrop', name)
        if (this._connected) {
            this._output_port();
        }
    }
    onRefInputDrop(name) {
        // if (name === 'stream') {
        if (this._unlisten_stream) {
            this._unlisten_stream();
            this._unlisten_stream = undefined;
        }
        if (this._stream) {
            this._stream = null;
            if (this._peer) {
                this._remove_stream(this._stream);
            }
        }
        // }
    }
    _send_data(data) {
        return __awaiter(this, void 0, void 0, function* () {
            return this._send({ type: 'data', data });
        });
    }
    _send(data) {
        return __awaiter(this, void 0, void 0, function* () {
            const message = (0, stringify_1.stringify)(data);
            this._peer.send(message);
            return;
        });
    }
    send(data) {
        return __awaiter(this, void 0, void 0, function* () {
            return this._send_data(data);
        });
    }
}
exports.default = PeerTransmitter;
//# sourceMappingURL=index.js.map