import Janus, { JanusJS } from "janus-gateway";
import LocalRTC from "../repositories/LocalRTC";
import PublisherEventsHandler from "../services/PublisherEventsHandler";
import Publisher from "../services/Publisher";
import Videoroom from "./Videoroom";
import SubVideoroom from "./SubVideoroom";
import { RoomExistsResponse, WhatToPublish } from "../types";
import RemoteFeeds from "../repositories/RemoteFeeds";
import eventEmitter from "../repositories/eventEmitter";
import { LOCAL_EVENTS } from "../events";
import { toHaveStyle } from "@testing-library/jest-dom/matchers";

class PubVideoroom extends Videoroom{
   
    protected readonly type = 'publisher';
    protected handler: JanusJS.PluginHandle | undefined;
    private publisher: Publisher | undefined;

    constructor (
        public readonly roomID: number,
        protected readonly userID: string,
        protected readonly janusSession: Janus,
        private readonly publishType: WhatToPublish
    ) {
        super();
    }

    public unpublish() {
        return this.publisher?.unpublish();
    }
    
    public switchByType(publishType: WhatToPublish) {
        this.publisher?.switchByType(publishType);
    }
    public switchByDevice(
        audioDeviceId: string | undefined,
        videoDeviceId: string | undefined
    ) {
        return this.publisher?.switchByDevice(audioDeviceId, videoDeviceId);
    }

    public muteAudio() {
        this.handler?.muteAudio();
    }
    public unmuteAudio() {
        this.handler?.unmuteAudio();
    }
    public muteVideo() {
        this.handler?.muteVideo();
    }
    public unmuteVideo() {
        this.handler?.unmuteVideo();
    }

    public async initiate(): Promise<JanusJS.PluginHandle> {
        await this.attachVideoroom();
        if( !(await this.checkRoomExists()) ){
            await this.createRoom();
        }
        await this.joinRoom();
        LocalRTC.setHandler(this.handler as JanusJS.PluginHandle); //to be removed
        return this.handler as JanusJS.PluginHandle;
    };

    protected attachVideoroom(): Promise<JanusJS.PluginHandle> {
        return new Promise<JanusJS.PluginHandle>((resolves: (value: JanusJS.PluginHandle) => void) => {
            this.janusSession.attach({
                plugin: 'janus.plugin.videoroom',
                opaqueId: this.userID.toString(),
                success: async (handle: JanusJS.PluginHandle): Promise<JanusJS.PluginCallbacks> => {
                    this.handler = handle;
                    eventEmitter.emit(LOCAL_EVENTS.HANDLER, handle);
                    await this.perpareRTC();
                    resolves(handle);
                    return {};
                },
                onmessage: async (msg: JanusJS.Message, jsep: JanusJS.JSEP) => {
                    await this.handleMessages(msg, jsep);
                },
                onlocaltrack(track, on) {
                    //I don't know if we should use them at all!
                    eventEmitter.emit(LOCAL_EVENTS.LOCAL_TRACK, track, on);
                },
                iceState: function(state) {
                    console.log("ICE state changed to " + state);
                },
                mediaState: function(medium, on, mid) {
                    console.log("Janus " + (on ? "started" : "stopped") + " receiving our " + medium + " (mid=" + mid + ")");
                },
                webrtcState: function(on) {
                    console.log("Janus says our WebRTC PeerConnection is " + (on ? "up" : "down") + " now");
                },
                slowLink: function(uplink, lost, mid) {
                    console.warn("Janus reports problems " + (uplink ? "sending" : "receiving") +
                        " packets on mid " + mid + " (" + lost + " lost packets)");
                },
            } as JanusJS.PluginOptions);
        });
    };

    private checkRoomExists(): Promise<boolean> {
        return new Promise<boolean>((resolves: (value: boolean)=> void ) => {
            const message = {
                'request': 'exists',
                'room': this.roomID
            };
            this.handler?.send({
                message,
                success: ({exists}: RoomExistsResponse) => {
                    resolves(exists);
                }
            });
        })
    }

    private createRoom(): Promise<void> {
        return new Promise<void>((resolves: ()=> void, rejects: (reason: string) => void) => {
            const message = {
                "request": "create",
                "room": this.roomID,
                "permanent": false,
                // "fir_freq": 10,
                "description": this.roomID.toString(),
                "is_private": false,
                'publishers': 3,
                'audiolevel_event': true,
                'notify_joining': true,
                'audio_active_packets': 100 
            };
            this.handler?.send({
                message,
                success: (res) => { 
                    if(res.error_code && res.error) {
                        rejects(res.error);
                        return;
                    }
                    resolves();
                }
            });
        });
    }
    private joinRoom(): Promise<void> {
        return new Promise<void>((resolve: () => void) => {
            const message = {
                "request": "join",
                "room": this.roomID,
                "ptype": this.type,
                "display": this.userID,
            };
            this.handler?.send({
                message,
                success: () => { resolve()},
            
            });
        });
    }

    protected async perpareRTC(): Promise<void> { 
        const handler = this.handler  as JanusJS.PluginHandle;
        this.publisher = new Publisher('publisher_front', handler, this.publishType);
    }

    protected async handleMessages(msg: JanusJS.Message, jsep: JanusJS.JSEP) : Promise<void> {
        new PublisherEventsHandler({
            msg,
            jsep,
            handleRemoteSDP: () => (this.handler as JanusJS.PluginHandle).handleRemoteJsep({jsep}),
            sendOffer: () => (this.publisher as Publisher).doOffering(),
            handlePublishers: (list: any) => this.handlePublishers(list),

        });
    }

    private async handlePublishers(list: any): Promise<void> {
        console.debug("Got a list of available publishers/feeds:", list);
        for(let p of list) {
            let rid = p.id;
            let streams = p.streams;
            let display = p.display;
            for(let i in streams) {
                let stream = streams[i];
                stream["id"] = rid;
                stream["display"] = display;
            }
            await this.handleNewRemoteFeed(rid, display, streams);
        }
    }; 

    private async handleNewRemoteFeed(rid: number, pubID: string, streams: any): Promise<void> {
        const subvideoroom = new SubVideoroom(
            this.roomID,
            pubID,
            this.janusSession,
            rid,
            streams
        );
        const subHandle = await subvideoroom.initiate();
        RemoteFeeds.setFeed({
            id: rid,
            userID: pubID,
            handler: subHandle,
            streams: streams
        });
    }
    
};

export default PubVideoroom;
