/*
 * https://github.com/morethanwords/tweb
 * Copyright (C) 2019-2021 Eduard Kuzmenko
 * https://github.com/morethanwords/tweb/blob/master/LICENSE
 * 
 * Originally from:
 * https://github.com/evgeny-nadymov/telegram-react
 * Copyright (C) 2018 Evgeny Nadymov
 * https://github.com/evgeny-nadymov/telegram-react/blob/master/LICENSE
 */

import indexOfAndSplice from '../../helpers/array/indexOfAndSplice';
import safeAssign from '../../helpers/object/safeAssign';
import { GroupCallParticipantVideoSourceGroup } from '../../layer';
import { fixMediaLineType, SDPBuilder, WebRTCLineType, WEBRTC_MEDIA_PORT } from './sdpBuilder';
import { AudioCodec, GroupCallConnectionTransport, Ssrc, UpdateGroupCallConnectionData, VideoCodec } from './types';

export class ConferenceEntry {
  public source: number;
  public sourceGroups: GroupCallParticipantVideoSourceGroup[];
  public transceiver: RTCRtpTransceiver;
  public originalDirection: RTCRtpTransceiverDirection;
  public direction: RTCRtpTransceiverDirection;
  public port: string;
  public endpoint: string;
  public peerId: PeerId;
  
  public sendEntry: ConferenceEntry;
  public recvEntry: ConferenceEntry;

  constructor(public mid: string, public type: WebRTCLineType) {
    this.port = WEBRTC_MEDIA_PORT;
  }

  public setDirection(direction: RTCRtpTransceiverDirection) {
    if(!this.originalDirection) {
      this.originalDirection = direction;
    }

    return this.direction = direction;
  }

  public setPort(port: string) {
    return this.port = port;
  }

  public setEndpoint(endpoint: string) {
    return this.endpoint = endpoint;
  }

  public setPeerId(peerId: PeerId) {
    return this.peerId = peerId;
  }

  public createTransceiver(connection: RTCPeerConnection, init?: RTCRtpTransceiverInit) {
    if(init?.direction) {
      this.setDirection(init.direction);
    }

    return this.transceiver = connection.addTransceiver(fixMediaLineType(this.type), init);
  }

  public setSource(source: number | GroupCallParticipantVideoSourceGroup[]) {
    let sourceGroups: GroupCallParticipantVideoSourceGroup[];
    if(Array.isArray(source)) {
      if(!source[0]) return;
      sourceGroups = source;
      source = sourceGroups[0].sources[0];
    }

    this.sourceGroups = sourceGroups;
    return this.source = source;
  }

  public shouldBeSkipped(isAnswer?: boolean) {
    return isAnswer && this.direction === 'inactive';
  }
}

export function generateSsrc(type: WebRTCLineType, source: number | GroupCallParticipantVideoSourceGroup[], endpoint?: string): Ssrc {
  let sourceGroups: GroupCallParticipantVideoSourceGroup[];
  if(Array.isArray(source)) {
    if(!source[0]) return;
    sourceGroups = source;
    source = sourceGroups[0].sources[0];
  }
  
  return {
    endpoint,
    type,
    source,
    sourceGroups,
  };
}

export default class LocalConferenceDescription implements UpdateGroupCallConnectionData {
  public readonly sessionId: string;
  // public ssrcs: Ssrc[];
  public readonly transport: GroupCallConnectionTransport;
  public readonly audio?: AudioCodec;
  public readonly video: VideoCodec;
  public readonly screencast?: VideoCodec;
  
  private maxSeenId: number;
  public readonly entries: ConferenceEntry[];
  private entriesByMid: Map<ConferenceEntry['mid'], ConferenceEntry>;
  private entriesBySource: Map<ConferenceEntry['source'], ConferenceEntry>;
  private entriesByPeerId: Map<ConferenceEntry['peerId'], Set<ConferenceEntry>>;
  
  constructor(public connection: RTCPeerConnection) {
    this.sessionId = '' + Date.now();
    // this.ssrcs = [];
    this.maxSeenId = -1;
    this.entries = [];
    this.entriesByMid = new Map();
    this.entriesBySource = new Map();
    this.entriesByPeerId = new Map();
  }

  public setData(data: UpdateGroupCallConnectionData) {
    return safeAssign(this, data);
  }

  public createEntry(type: WebRTCLineType) {
    const mid = '' + ++this.maxSeenId;
    const entry = new ConferenceEntry(mid, type);
    this.entries.push(entry);
    this.entriesByMid.set(mid, entry);
    return entry;
  }

  public deleteEntry(entry: ConferenceEntry) {
    indexOfAndSplice(this.entries, entry);
    this.entriesByMid.delete(entry.mid);
    this.entriesBySource.delete(entry.source);

    const set = this.entriesByPeerId.get(entry.peerId);
    if(set) {
      set.delete(entry);
      if(!set.size) {
        this.entriesByPeerId.delete(entry.peerId);
      }
    }
  }

  public setEntrySource(entry: ConferenceEntry, source: Parameters<ConferenceEntry['setSource']>[0]) {
    entry.setSource(source);
    this.entriesBySource.set(entry.source, entry);
  }

  public setEntryPeerId(entry: ConferenceEntry, peerId: ConferenceEntry['peerId']) {
    entry.setPeerId(peerId);
    let set = this.entriesByPeerId.get(peerId);
    if(!set) {
      this.entriesByPeerId.set(peerId, set = new Set());
    }

    set.add(entry);
  }
  
  public findEntry(verify: Parameters<LocalConferenceDescription['entries']['find']>[0]) {
    return this.entries.find(verify);
  }

  public findFreeSendRecvEntry(type: WebRTCLineType, isSending: boolean) {
    let entry = this.entries.find((entry) => {
      return entry.direction === 'sendrecv' && entry.type === type && !(isSending ? entry.sendEntry : entry.recvEntry);
    });

    if(!entry) {
      entry = this.createEntry(type);
      entry.setDirection('sendrecv');
    }

    return entry;
  }
  
  public getEntryByMid(mid: ConferenceEntry['mid']) {
    return this.entriesByMid.get(mid);
  }

  public getEntryBySource(source: ConferenceEntry['source']) {
    return this.entriesBySource.get(source);
  }

  public getEntriesByPeerId(peerId: ConferenceEntry['peerId']) {
    return this.entriesByPeerId.get(peerId);
  }

  public generateSdp(options: Omit<Parameters<SDPBuilder['addConference']>[0], 'conference'>) {
    return SDPBuilder.fromConference({
      conference: this,
      ...options
    });
  }
}
