import WebSocketService from "./websocket.service";
import { EventEmitter } from "events";


let singleton: CallManager = null;

export default class CallManager {
  private inCall: boolean;
  public peerId: number;
  private localStream: MediaStream;
  private remoteStream: MediaStream;
  private signalingChannel;
  private emitter = new EventEmitter();
  private pc: RTCPeerConnection;
  public child;
  public peer = { avatar: '' };
  public isHost = false;
  public books = [];
  public currentActivity = null;
  public callId;
  private userId: number
  constructor(private ws: WebSocketService) {
    this.inCall = false;
    this.peerId = null;
    this.pc = null;
    this.remoteStream = new MediaStream();
  }


  async connectToCall(userId: number, mediaConstraints: MediaStreamConstraints, callId: number): Promise<boolean> {
    if (this.inCall) throw new Error('Already connected to call');
    this.callId = callId;
    this.userId = userId;
    console.log('connecting to call');
    await this.getUserMedia(mediaConstraints);
    this.signalingChannel = this.ws.subscribe(`call:${this.callId}`);
    const signalingChannel = this.signalingChannel;
    const self = this;
    return new Promise((resolve, reject) => {
      signalingChannel.on('close', self.close.bind(self));
      signalingChannel.on('call:start', self.onCallStart.bind(self));
      signalingChannel.on('call:standby', self.onCallStandby.bind(self));
      signalingChannel.on('wrtc:sdp:offer', self.onRemoteOffer.bind(self));
      signalingChannel.on('wrtc:sdp:answer', self.onRemoteAnswer.bind(self));
      signalingChannel.on('wrtc:ice', self.onRemoteIce.bind(self));
      signalingChannel.on('book:action:flip-page', self.onActionBookFlip.bind(self));
      signalingChannel.on('call:host:changed', self.onRemoteHostChanged.bind(self));
      signalingChannel.on('call:view:lobby', self.onRemoteViewLobby.bind(self));
      signalingChannel.on('call:view:book', self.onRemoteViewBook.bind(self));
      signalingChannel.on('error', (e) => {
        console.error(e);
        resolve(false)
      });
      signalingChannel.on('ready', () => {
        console.log('in Ready');
        self.inCall = true;
        resolve(true)
      });
    });
  }
  on(event: ECallEvents, callback: (...args) => void) {
    this.emitter.on(event, callback);
  }
  removeListener(event: ECallEvents, callback: (...args) => void) {
    this.emitter.removeListener(event, callback);
  }
  private emit(event: ECallEvents, data: any) {
    this.emitter.emit(event, data);
  }
  private send(event: string, payload: { [key: string]: any }) {
    console.log(`Sending event: ${event}`);
    this.signalingChannel.emit(event, {
      userId: this.userId,
      peerId: this.peerId,
      ...payload
    })
  }
  async onCallStart(payload: { iceServers: RTCIceServer[], peerId: number, users: any[], child: any, hostId: number, books: any[] }) {
    console.log('onCallStart');
    console.log(payload);
    this.peerId = payload.peerId;
    this.books = payload.books;
    this.isHost = this.userId === payload.hostId;
    this.pc = new RTCPeerConnection({ iceServers: payload.iceServers });
    this.child = payload.child;
    payload.users.forEach(u => {
      if (u.id === this.peerId) this.peer = u;
    });
    this.emit(ECallEvents.CALL_HOST_CHANGED, { hostId: payload.hostId });
    console.log('Created PeerConnection');
    console.log('adding tracks to pc');
    this.localStream.getTracks().forEach(t => this.pc.addTrack(t, this.localStream));
    this.setupPeerConnectionListeners();
    const sdp = await this.pc.createOffer();
    await this.pc.setLocalDescription(sdp);
    console.log('Local description Set', sdp.sdp);
    this.send('wrtc:sdp:offer', {
      sdp
    });
    return true;
  }

  async onCallStandby(payload: { iceServers: RTCIceServer[], peerId: number, users: any[], child: any, hostId: number, books: any[] }) {
    console.log('onCallStandby');
    console.log(payload);
    this.peerId = payload.peerId;
    this.books = payload.books;
    this.isHost = this.userId === payload.hostId;
    this.pc = new RTCPeerConnection({ iceServers: payload.iceServers });
    this.child = payload.child;
    payload.users.forEach(u => {
      if (u.id === this.peerId) this.peer = u;
    });
    this.emit(ECallEvents.CALL_HOST_CHANGED, { hostId: payload.hostId });
    console.log('Created PeerConnection');
    console.log('adding tracks to pc');
    this.localStream.getTracks().forEach(t => this.pc.addTrack(t, this.localStream));
    this.setupPeerConnectionListeners();
    return true;
  }

  public selectBook(index: number, remote: boolean) {
    this.currentActivity = this.books[index];
    console.log('-------> Selected Book ', index, 'bookId:', this.currentActivity.id);
    if (!remote) this.send('call:view:book', { bookId: this.currentActivity.id });
  }
  public backToLobby() {
    console.log('-------> backToLobby');
    this.emitter.removeAllListeners(ECallEvents.ACTION_BOOK_FLIP);
    this.send('call:view:lobby', {});
  }

  setupPeerConnectionListeners() {
    this.pc.addEventListener("icecandidate", this.onLocalIce.bind(this));
    this.pc.addEventListener('connectionstatechange', event => {
      console.log(`PC Connection state: ${this.pc.connectionState}`);
      // if (this.pc.connectionState === 'connected') {
      //   // Peers connected!
      // }
    });

    this.pc.addEventListener('iceconnectionstatechange', event => {
      console.log('iceconnectionstatechange');
      console.log(this.pc.iceConnectionState);
    })

    this.pc.addEventListener('track', async (event) => {
      console.log('On remote track!');
      this.remoteStream.addTrack(event.track);
    });

    this.pc.addEventListener('icegatheringstatechange', event => {
      console.log('icegatheringstatechange', this.pc.iceGatheringState);
    });
  }

  onLocalIce(event) {
    if (event.candidate) {
      console.log('Sending candidate');
      this.send('wrtc:ice', {
        ice: event.candidate,
      });
    }

  }
  async onRemoteOffer(payload) {
    const offer = new RTCSessionDescription(payload.sdp);
    await this.pc.setRemoteDescription(offer);
    console.log('Remote offer Set', offer.sdp);
    const sdp = await this.pc.createAnswer();
    this.send('wrtc:sdp:answer', {
      sdp
    });
    await this.pc.setLocalDescription(sdp);
    console.log('Local answer Set', sdp.sdp);
    return true;
  }
  async onRemoteAnswer(payload) {
    const answer = new RTCSessionDescription(payload.sdp);
    await this.pc.setRemoteDescription(answer);
    console.log('Remote answer Set', answer.sdp);
    return true;
  }
  async onRemoteIce(payload) {
    const ice = payload.ice;
    await this.pc.addIceCandidate(ice);
    return true;
  }

  async getUserMedia(constraints: MediaStreamConstraints = { video: true, audio: true }) {
    if (this.localStream) return this.localStream;
    this.localStream = await navigator.mediaDevices.getUserMedia(constraints);
    return this.localStream;
  }

  getRemoteStream() {
    return this.remoteStream;
  }

  onActionBookFlip(payload) {
    this.emit(ECallEvents.ACTION_BOOK_FLIP, payload);
  };

  changeHost() {
    this.send('call:host:changed', {});
  }

  onRemoteHostChanged(payload) {
    this.isHost = this.userId === payload.hostId;
    this.emit(ECallEvents.CALL_HOST_CHANGED, payload);
  }

  onRemoteViewLobby(payload) {
    this.emitter.removeAllListeners(ECallEvents.ACTION_BOOK_FLIP);
    this.emit(ECallEvents.CALL_VIEW_LOBBY, null);
  }
  onRemoteViewBook(payload) {
    this.emit(ECallEvents.CALL_VIEW_BOOK, payload);
  }

  close() {
    if (!this.inCall) return;
    console.log('Closing...');
    this.emit(ECallEvents.CLOSE, this.callId);
    if (this.signalingChannel) this.signalingChannel.close();
    this.signalingChannel = null;
    if (this.pc) this.pc.close();
    if (this.localStream) this.localStream.getTracks().forEach(t => t.stop());
    this.localStream = null;
    this.remoteStream = null;
    this.emitter.removeAllListeners();
    this.inCall = false;
    singleton = null;
  }

}

export enum ECallEvents {
  CLOSE = 'CLOSE',
  REMOTE_STREAM = 'REMOTE_STREAM',
  ACTION_BOOK_FLIP = 'ACTION_BOOK_FLIP',
  CALL_HOST_CHANGED = "CALL_HOST_CHANGED",
  CALL_VIEW_LOBBY = 'CALL_VIEW_LOBBY',
  CALL_VIEW_BOOK = 'CALL_VIEW_BOOK',
}
