"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 });
exports.Peer = exports.waitAllCandidates = void 0;
const _1 = require("../../Class/$");
const APINotImplementedError_1 = require("../../exception/APINotImplementedError");
function waitAllCandidates(rtc) {
    return __awaiter(this, void 0, void 0, function* () {
        return new Promise((resolve, reject) => {
            const candidates = [];
            const listener = ({ candidate }) => __awaiter(this, void 0, void 0, function* () {
                if (candidate) {
                    candidates.push(candidate);
                }
                else {
                    rtc.removeEventListener('icecandidate', listener);
                    resolve(candidates);
                }
            });
            rtc.addEventListener('icecandidate', listener);
        });
    });
}
exports.waitAllCandidates = waitAllCandidates;
class Peer extends _1.$ {
    constructor(system, initiatior, config) {
        super(system);
        this._initiator = initiatior;
        this._config = config;
        if (!RTCPeerConnection) {
            throw new APINotImplementedError_1.APINotSupportedError('Web RTC');
        }
        const rtc = new RTCPeerConnection(this._config);
        this._setup(rtc);
        this._rtc = rtc;
        const channel = this._rtc.createDataChannel('data', {
            negotiated: true,
            id: 0,
        });
        channel.onmessage = (event) => {
            const { data } = event;
            const _data = JSON.parse(data);
            const { type, data: __data } = _data;
            switch (type) {
                case 'message':
                    {
                        this.emit('message', __data);
                    }
                    break;
                case 'start':
                    if (this._incoming_stream) {
                        this.emit('start', this._incoming_stream);
                    }
                    break;
                case 'stop':
                    this.emit('stop');
                    break;
            }
        };
        channel.onopen = () => {
            this.emit('connect');
            if (this._stream) {
                const message = JSON.stringify({ type: 'start' });
                this._rtc_channel.send(message);
            }
        };
        if (this._initiator) {
            const stream = new MediaStream();
            this._outgoing_stream = stream;
            const video_transceiver = this._rtc.addTransceiver('video', {
                streams: [stream],
            });
            this._video_transceiver = video_transceiver;
            const audio_transceiver = this._rtc.addTransceiver('audio', {
                streams: [stream],
            });
            this._audio_transceiver = audio_transceiver;
        }
        this._rtc_channel = channel;
    }
    send(message) {
        const _message = JSON.stringify({ type: 'message', data: message });
        this._rtc_channel.send(_message);
    }
    offer() {
        return __awaiter(this, void 0, void 0, function* () {
            return this._offer(this._rtc);
        });
    }
    _offer(rtc) {
        return __awaiter(this, void 0, void 0, function* () {
            const offer = yield rtc.createOffer({
                offerToReceiveAudio: true,
                offerToReceiveVideo: true,
            });
            yield rtc.setLocalDescription(offer);
            const { sdp } = offer;
            const candidates = yield waitAllCandidates(rtc);
            const signal = JSON.stringify({ sdp, candidates });
            return signal;
        });
    }
    _setup(rtc) {
        // rtc.oniceconnectionstatechange = () => {
        //   console.log('iceConnectionState', rtc.iceConnectionState)
        // }
        // rtc.onicegatheringstatechange = async (event) => {
        //   const { iceGatheringState } = rtc
        //   console.log('onicegatheringstatechange', iceGatheringState)
        //   if (iceGatheringState === 'complete') {
        //   }
        // }
        // rtc.onicecandidate = async ({ candidate }) => {
        //   console.log('onicecandidate', candidate)
        // }
        rtc.onconnectionstatechange = () => {
            const { connectionState } = rtc;
            // console.log('onconnectionstatechange', connectionState)
            switch (connectionState) {
                case 'connected':
                    // this.emit('connect')
                    break;
                case 'disconnected':
                case 'closed':
                    this.emit('close');
                    break;
            }
        };
        // rtc.onnegotiationneeded = () => {
        //   console.log('onnegotiationneeded')
        // }
        // rtc.ondatachannel = (event: RTCDataChannelEvent) => {
        //   // console.log('ondatachannel', event)
        //   const { channel } = event
        // }
        if (!this._initiator) {
            rtc.ontrack = (event) => {
                // console.log('ontrack', event)
                const { streams } = event;
                const stream = streams[0];
                if (stream) {
                    this._incoming_stream = stream;
                }
            };
        }
    }
    _plunk(rtc) {
        rtc.oniceconnectionstatechange = null;
        rtc.onconnectionstatechange = null;
        rtc.onicegatheringstatechange = null;
        rtc.onnegotiationneeded = null;
        rtc.ondatachannel = null;
        rtc.ontrack = null;
        // rtc.close()
    }
    _plunk_channel(rtc_channel) {
        rtc_channel.onmessage = null;
    }
    acceptOffer(signal) {
        return __awaiter(this, void 0, void 0, function* () {
            return this._acceptOffer(this._rtc, signal);
        });
    }
    _acceptOffer(rtc, signal) {
        return __awaiter(this, void 0, void 0, function* () {
            const { sdp, candidates } = JSON.parse(signal);
            const offer = new RTCSessionDescription({ type: 'offer', sdp });
            yield rtc.setRemoteDescription(offer);
            for (const candidate of candidates) {
                yield rtc.addIceCandidate(candidate);
            }
        });
    }
    answer() {
        return __awaiter(this, void 0, void 0, function* () {
            return this._answer(this._rtc);
        });
    }
    _answer(rtc) {
        return __awaiter(this, void 0, void 0, function* () {
            const answer = yield rtc.createAnswer({
                offerToReceiveAudio: true,
                offerToReceiveVideo: true,
            });
            rtc.setLocalDescription(answer);
            const { sdp } = answer;
            const candidates = yield waitAllCandidates(rtc);
            const signal = JSON.stringify({ sdp, candidates });
            return signal;
        });
    }
    acceptAnswer(signal) {
        return __awaiter(this, void 0, void 0, function* () {
            return this._acceptAnswer(this._rtc, signal);
        });
    }
    _acceptAnswer(rtc, signal) {
        return __awaiter(this, void 0, void 0, function* () {
            const { sdp, candidates } = JSON.parse(signal);
            const answer = new RTCSessionDescription({ type: 'answer', sdp });
            yield rtc.setRemoteDescription(answer);
            for (const candidate of candidates) {
                yield rtc.addIceCandidate(candidate);
            }
            return;
        });
    }
    addStream(stream) {
        return __awaiter(this, void 0, void 0, function* () {
            this._stream = stream;
            const audio_track = stream.getAudioTracks()[0];
            if (audio_track) {
                yield this._audio_transceiver.sender.replaceTrack(audio_track);
                // this._audio_transceiver.sender.track.enabled = true
            }
            const video_track = stream.getVideoTracks()[0];
            if (video_track) {
                yield this._video_transceiver.sender.replaceTrack(video_track);
                // this._video_transceiver.sender.track.enabled = true
            }
            if (this._rtc_channel.readyState === 'open') {
                const message = JSON.stringify({ type: 'start' });
                this._rtc_channel.send(message);
            }
        });
    }
    removeStream() {
        return __awaiter(this, void 0, void 0, function* () {
            this._stream = null;
            // this._audio_transceiver.sender.track.enabled = false
            // this._video_transceiver.sender.track.enabled = false
            yield Promise.all([
                this._video_transceiver.sender.replaceTrack(null),
                this._audio_transceiver.sender.replaceTrack(null),
            ]);
            if (this._rtc_channel.readyState === 'open') {
                const message = JSON.stringify({ type: 'stop' });
                this._rtc_channel.send(message);
            }
        });
    }
    close() {
        this._plunk_channel(this._rtc_channel);
        this._rtc_channel.close();
        this._plunk(this._rtc);
        this._rtc.close();
    }
}
exports.Peer = Peer;
//# sourceMappingURL=Peer.js.map