import { sleep, getUniqueId } from '../helpers/testutils.js';

export class PeerConnectionMock extends EventTarget {
  constructor(config) {
    super();
    this.delay = async () => await sleep(10);
    this.config = config;
    this.ontrack = undefined;
    this.ondatachannel = undefined;
    this.onicecandidate = undefined;
    this.onnegotiationneeded = undefined;
    this.onsignalingstatechange = undefined;
    this.oniceconnectionstatechange = undefined;
    this.onicegatheringstatechange = undefined;
    this.pendingLocalDescription = null;
    this.currentLocalDescription = null;
    this.pendingRemoteDescription = null;
    this.currentRemoteDescription = null;
    this.candidates = [];
    this.signalingState = "stable";
    this.iceConnectionState = "new";
    this.iceGatheringState = "new";
    this.audioTracks = new Map();
    this.videoTracks = new Map();
    this.channels = new Map();
    this.transceiverCount = 0;
    this.transceivers = new Map();
  }

  get localDescription() {
    if (this.pendingLocalDescription) {
      return this.pendingLocalDescription;
    }

    return this.currentLocalDescription;
  }

  get remoteDescription() {
    if (this.pendingRemoteDescription) {
      return this.pendingRemoteDescription;
    }

    return this.currentRemoteDescription;
  }

  close() {
    this.ontrack = undefined;
    this.ondatachannel = undefined;
    this.onicecandidate = undefined;
    this.onnegotiationneeded = undefined;
    this.onsignalingstatechange = undefined;
    this.oniceconnectionstatechange = undefined;
    this.onicegatheringstatechange = undefined;
    this.pendingLocalDescription = null;
    this.currentLocalDescription = null;
    this.pendingRemoteDescription = null;
    this.currentRemoteDescription = null;
    this.candidates = [];
    this.signalingState = "close";
    this.iceConnectionState = "closed";
    this.audioTracks.clear();
    this.videoTracks.clear();
    this.channels.clear();
    this.transceiverCount = 0;
    this.transceivers.clear();
  }

  fireOnNegotiationNeeded() {
    if (this.onnegotiationneeded) {
      this.onnegotiationneeded();
    }
  }

  getTransceivers() {
    return Array.from(this.transceivers.values());
  }

  addTrack(track) {
    if (track.kind == "audio") {
      this.audioTracks.set(track.id, track);
    } else {
      this.videoTracks.set(track.id, track);
    }
    const transceiver = { direction: "sendrecv", sender: { track: track }, receiver: null, setCodecPreferences: (codecs) => { console.log(codecs); } };
    this.transceivers.set(this.transceiverCount++, transceiver);
    this.fireOnNegotiationNeeded();
    return transceiver.sender;
  }

  addTransceiver(trackOrKind) {
    if (typeof trackOrKind == "string") {
      const track = { id: getUniqueId(), kind: trackOrKind };
      if (track.kind == "audio") {
        this.audioTracks.set(track.id, track);
      } else {
        this.videoTracks.set(track.id, track);
      }
      const transceiver = { direction: "sendrecv", sender: { track: track }, receiver: null, setCodecPreferences: (codecs) => { console.log(codecs); } };
      this.transceivers.set(this.transceiverCount++, transceiver);
      this.fireOnNegotiationNeeded();
      return transceiver;
    }

    if (trackOrKind.kind == "audio") {
      this.audioTracks.set(trackOrKind.id, trackOrKind);
    } else {
      this.videoTracks.set(trackOrKind.id, trackOrKind);
    }
    const transceiver = { direction: "sendrecv", sender: { track: trackOrKind }, receiver: null, setCodecPreferences: (codecs) => { console.log(codecs); } };
    this.transceivers.set(this.transceiverCount++, transceiver);
    this.fireOnNegotiationNeeded();
    return transceiver;
  }

  createDataChannel(label) {
    const channel = { id: getUniqueId(), label: label };
    this.channels.set(channel.id, channel);
    this.fireOnNegotiationNeeded();
    return channel;
  }

  async setLocalDescription(description = null) {
    if (description == null) {
      description = this._createSessionDescription();
    }
    await this.delay();
    this._setSessionDescription(description, false);
  }

  async setRemoteDescription(description) {
    await this.delay();
    if (description.type == "offer" && this.signalingState == "have-local-offer") {
      this._setSessionDescription({ type: "rollback", sdp: "" }, true);
    }
    this._setSessionDescription(description, true);
  }

  _createSessionDescription() {
    let dummySdp = "testsdp";
    if (this.videoTracks.size > 0) {
      dummySdp += "videotrack";
    }
    if (this.audioTracks.size > 0) {
      dummySdp += "audiotrack";
    }
    if (this.channels.size > 0) {
      dummySdp += "datachannel";
    }

    if (this.signalingState == "stable" || this.signalingState == "have-local-offer" || this.signalingState == "have-remote-pranswer") {
      return { type: "offer", sdp: dummySdp };
    }
    return { type: "answer", sdp: dummySdp };
  }

  _setSessionDescription(description, remote) {
    if (description.type == "rollback"
      && (this.signalingState == "stable" || this.signalingState == "have-local-pranswer" || this.signalingState == "have-remote-pranswer")) {
      throw "InvalidStateError";
    }

    if (description.type != "rollback") {
      if (remote) {
        if (description.type == "offer") {
          this.pendingRemoteDescription = description;
          this.signalingState = "have-remote-offer";
          this.onsignalingstatechange(this.signalingState);
          // if sdp contains track string, create dummy track
          if (description.sdp.includes("track")) {
            const isVideo = description.sdp.includes("video");
            const kind = isVideo ? "video" : "audio";
            this._createTrackAndTransceiver(kind);
          }
          if (description.sdp.includes("datachannel")) {
            const channel = { id: getUniqueId(), label: "dummychannel" };
            this.channels.set(channel.id, channel);
          }
        }
        if (description.type == "answer") {
          this.currentRemoteDescription = description;
          this.currentLocalDescription = this.pendingLocalDescription;
          this.pendingLocalDescription = null;
          this.pendingRemoteDescription = null;
          this.signalingState = "stable";
          this.onsignalingstatechange(this.signalingState);
        }
        if (description.type == "pranswer") {
          this.pendingRemoteDescription = description;
          this.signalingState = "have-remote-pranswer";
          this.onsignalingstatechange(this.signalingState);
        }
      } else {
        if (description.type == "offer") {
          this.pendingLocalDescription = description;
          this.signalingState = "have-local-offer";
          this.onsignalingstatechange(this.signalingState);
        }
        if (description.type == "answer") {
          this.currentLocalDescription = description;
          this.currentRemoteDescription = this.pendingRemoteDescription;
          this.pendingLocalDescription = null;
          this.pendingRemoteDescription = null;
          this.signalingState = "stable";
          this.onsignalingstatechange(this.signalingState);
          // if sdp contains track string, create dummy track
          if (description.sdp.includes("track")) {
            const isVideo = description.sdp.includes("video");
            const kind = isVideo ? "video" : "audio";
            this._createTrackAndTransceiver(kind);
          }
          if (description.sdp.includes("datachannel")) {
            const channel = { id: getUniqueId(), label: "dummychannel" };
            this.channels.set(channel.id, channel);
          }
        }
        if (description.type == "pranswer") {
          this.pendingLocalDescription = description;
          this.signalingState = "have-local-pranswer";
          this.onsignalingstatechange(this.signalingState);
        }
      }
    } else {
      this.pendingLocalDescription = null;
      this.pendingRemoteDescription = null;
      this.signalingState = "stable";
      this.onsignalingstatechange(this.signalingState);
    }

    if (this.videoTracks.size != 0 || this.audioTracks.size != 0) {
      this._mockGatheringIceCandidate(this.videoTracks.size + this.audioTracks.size);
    }

    //fire ontrack with new tracks, after using tracks clear.
    if (this.ontrack) {
      for (const track of this.videoTracks.values()) {
        this.ontrack({ track: track });
      }
      this.videoTracks.clear();

      for (const track of this.audioTracks.values()) {
        this.ontrack({ track: track });
      }
      this.audioTracks.clear();
    }

    if (this.ondatachannel) {
      for (const channel of this.channels.values()) {
        this.ondatachannel({ channel: channel });
      }
      this.channels.clear();
    }
  }

  async _mockGatheringIceCandidate(count) {
    this.iceGatheringState = "gathering";
    if (this.onicegatheringstatechange) {
      this.onicegatheringstatechange(this.iceGatheringState);
    }
    for (let index = 0; index < count; index++) {
      await this.delay();
      const newCandidate = { candidate: getUniqueId(), sdpMLineIndex: index, sdpMid: index };
      if (this.onicecandidate) {
        this.onicecandidate(newCandidate);
      }
    }
    this.iceGatheringState = "complete";
    if (this.onicegatheringstatechange) {
      this.onicegatheringstatechange(this.iceGatheringState);
    }
    if (this.onicecandidate) {
      this.onicecandidate({ candidate: null, sdpMLineIndex: null, sdpMid: null });
    }
  }

  async addIceCandidate(candidate) {
    await this.delay();
    if (this.remoteDescription == null) {
      throw "InvalidStateError";
    }
    this.candidates.push(candidate);
  }

  _createTrackAndTransceiver(kind) {
    const track = { id: getUniqueId(), kind: kind };
    if (kind == "video") {
      this.videoTracks.set(track.id, track);
    } else {
      this.audioTracks.set(track.id, track);
    }
    const transceiver = { direction: "sendrecv", sender: { track: track }, receiver: null, setCodecPreferences: (codecs) => { console.log(codecs); } };
    this.transceivers.set(this.transceiverCount++, transceiver);
  }
}

export class SessionDescriptionMock {

  constructor(object) {
    this.sdp = object.sdp;
    this.type = object.type;
  }

  sdp;
  type;
}

export class IceCandidateMock {
  constructor(object) {
    this.candidate = object.candidate;
    this.sdpMLineIndex = object.sdpMLineIndex;
    this.sdpMid = object.sdpMid;
  }

  candidate;
  sdpMLineIndex;
  sdpMid;
}
