import EventEmitter from 'events';
import AgoraRTC from 'agora-rtc-sdk';
import { roomStore } from '../stores/room';

if (process.env.REACT_APP_AGORA_LOG !== 'true') {
  AgoraRTC.Logger.setLogLevel(AgoraRTC.Logger.NONE);
}
export interface AgoraStreamSpec {
  streamID: number
  video: boolean
  audio: boolean
  mirror?: boolean
  screen?: boolean
  microphoneId?: string
  cameraId?: string
  audioOutput?: {
    volume: number
    deviceId: string
  }
}

const streamEvents: string[] = [
  "accessAllowed",
  "accessDenied",
  "stopScreenSharing",
  "videoTrackEnded",
  "audioTrackEnded",
  "player-status-changed"
];

const clientEvents: string[] = [
  'stream-published',
  'stream-added',
  'stream-removed',
  'stream-subscribed',
  'peer-online',
  'peer-leave',
  'error',
  'network-type-changed',
  'network-quality',
  'exception',
  'onTokenPrivilegeWillExpire',
  'onTokenPrivilegeDidExpire',
]

export const APP_ID = process.env.REACT_APP_AGORA_APP_ID as string;
export const APP_TOKEN = process.env.REACT_APP_AGORA_APP_TOKEN as string;
export const ENABLE_LOG = process.env.REACT_APP_AGORA_LOG as string === "true";
export const SHARE_ID = 0;
const BindEventMap: any = {};
class AgoraRTCClient {

  private streamID: any;
  public _init: boolean = false;
  public _joined: boolean = false;
  public _published: boolean = false;
  private _internalTimer: NodeJS.Timeout | any;
  public _client: any = AgoraRTC.createClient({mode: 'live', codec: 'vp8'});
  public _bus: EventEmitter = new EventEmitter();
  public _localStream: any = null;
  public _streamEvents: string[];
  public _clientEvents: string[];
  // public _enableBeauty = false;
  constructor () {
    this.streamID = null;
    this._streamEvents = [];
    this._clientEvents = [];
  }

  // init rtc client when _init flag is false;
  async initClient(appId: string) {
    if (this._init) return;
    let prepareInit = new Promise((resolve, reject) => {
      this._init === false && this._client.init(appId, () => {
        this._init = true;
        resolve()
      }, reject);
    })
    await prepareInit;
  }

  // create rtc client;
  async createClient(appId: string, enableRtt?: boolean) {
    await this.initClient(appId);
    this.subscribeClientEvents();
    if (enableRtt) {
      this._internalTimer = setInterval(() => {
        this._client.getTransportStats((stats: any) => {
          const RTT = stats.RTT ? stats.RTT : 0;
          this._bus.emit('watch-rtt', RTT);
        });
      }, 100);
    }
  }

  destroyClient(): void {
    this.unsubscribeClientEvents();
  }

  subscribeClientEvents() {
    for (let evtName of clientEvents) {
      this._clientEvents.push(evtName);
      this._client.on(evtName, (args: any) => {
        // console.log("[agora-web] " +evtName+ ": ", args);
        if (evtName === "peer-leave") {
          console.log("[agora-web] peer-leave: ", args);
        }

        this._bus.emit(evtName, args);
      });
    }
  }

  unsubscribeClientEvents() {
    for (let evtName of this._clientEvents) {
      this._client.off(evtName, () => {});
      this._clientEvents = this._clientEvents.filter((it: any) => it === evtName);
    }
  }

  subscribeLocalStreamEvents() {
    for (let evtName of streamEvents) {
      this._streamEvents.push(evtName);
      this._localStream.on(evtName, (args: any) => {
        this._bus.emit(evtName, args);
      });
    }
  }

  unsubscribeLocalStreamEvents() {
    if (this._localStream) {
      for (let evtName of this._streamEvents) {
        this._localStream.removeEventListener(evtName, (args: any[]) => {});
        this._streamEvents = this._streamEvents.filter((it: any) => it === evtName);
      }
    }
  }

  renewToken(newToken: string) {
    if (!this._client) return console.warn('renewToken is not permitted, checkout your instance');
    this._client.renewToken(newToken);
  }

  removeAllListeners() {
    this.unsubscribeClientEvents();
    this._bus.removeAllListeners();
  }
  offAll() {
    for (const evtName of Object.keys(BindEventMap)) {
      const cb = BindEventMap[evtName];
      this._bus.off(evtName, cb);
    }
  }
  // subscribe
  on(evtName: string, cb: (args: any) => void) {
    BindEventMap[evtName] = cb;
    this._bus.on(evtName, cb);
  }

  // unsubscribe
  off(evtName: string, cb: (args: any) => void) {
    this._bus.off(evtName, cb);
  }

  async publish() { console.log('rtc publish')
    return new Promise((resolve, reject) => {
      if (this._published) {
        return resolve();
      }
      this._client.publish(this._localStream, (err: any) => {
        reject(err);
      })
      setTimeout(() => {
        resolve();
        this._published = true;
      }, 300);
    })
  }

  async unpublish() {console.log('unpublish' , 1);
    return new Promise((resolve, reject) => {
      if (!this._published || !this._localStream) {
        return resolve();
      }
      if (this._localStream ) {
        if (this._localStream._enableBeauty) {
          // this._localStream.setBeautyEffectOptions(false);
          this._localStream._enableBeauty = false
        }
      }
      this._client.unpublish(this._localStream, (err: any) => {
        reject(err);
      })
      setTimeout(() => {
        resolve();
        this.destroyLocalStream();
        this._published = false;
      }, 300);
    })
  }

  setRemoteVideoStreamType(stream: any, streamType: number) {
    this._client.setRemoteVideoStreamType(stream, streamType);
  }

  async enableDualStream() {
    return new Promise((resolve, reject) => {
      this._client.enableDualStream(resolve, reject);
    });
  }

  createLocalStream(data: AgoraStreamSpec): Promise<any> {
    this._localStream = AgoraRTC.createStream({...data, mirror: false});
    this._localStream.setVideoEncoderConfiguration({
      // 视频分辨率
      resolution: {
        width: 160,
        height: 120
      },
      // 视频编码帧率。通常建议是 15 帧，不超过 30 帧
      frameRate: {
        min: 15,
        max: 30
      },
      // 码率。一般情况下推荐使用标准码率
      bitrate: {
        min: 100,
        max: 150
      }
    });
    return new Promise((resolve, reject) => {
      this._localStream.init(async () => {
        this.streamID = data.streamID;


        /*
        if (roomStore.state.me.role == 'teacher') {
          const result = await this._localStream.setBeautyEffectOptions(true, {
            lighteningContrastLevel: 2,
            lighteningLevel: 0.9,
            rednessLevel: 0.2,
            smoothnessLevel: 0.9
          })
          this._localStream._enableBeauty = true;
          // this._enableBeauty = true;
          console.log('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%', result)
        }*/
        this.subscribeLocalStreamEvents();
        if (data.audioOutput && data.audioOutput.deviceId) {
          this.setAudioOutput(data.audioOutput.deviceId).then(() => {
            resolve();
          }).catch((err: any) => {
            reject(err);
          })
        }
        resolve();
      }, (err: any) => {
        reject(err);
      })
    });
  }

  destroyLocalStream () {
    this.unsubscribeLocalStreamEvents();
    if(this._localStream) {
      if (this._localStream.isPlaying()) {
        this._localStream.stop();
      }
      this._localStream.close();
    }
    this._localStream = null;
    this.streamID = 0;
  }

  async join (uid: number, channel: string, token?: string) {
    return new Promise((resolve, reject) => {
      this._client.join(token, channel, +uid, resolve, reject);
    })
  }

  async leave () { console.log(9999, 'rtc leave', roomStore.state.me);
    if (this._client) {
      return new Promise((resolve, reject) => {
        this._client.leave(() => {

          resolve()
        }, reject);
      })
    }
  }

  setAudioOutput(speakerId: string) {
    return new Promise((resolve, reject) => {
      this._client.setAudioOutput(speakerId, resolve, reject);
    })
  }

  setAudioVolume(volume: number) {
    this._client.setAudioVolume(volume);
  }

  subscribe(stream: any) {
    this._client.subscribe(stream, {video: true, audio: true}, (err: any) => {
      console.log('[rtc-client] subscribe failed: ', JSON.stringify(err));
    });
  }

  destroy (): void {
    this._internalTimer && clearInterval(this._internalTimer);
    this._internalTimer = null;
    this.destroyLocalStream();
  }

  async exit () {
    await this.leave();
    await this.destroy();
  }

  getDevices (): Promise<Device[]> {
    return new Promise((resolve, reject) => {
      AgoraRTC.getDevices((devices: any) => {
        const _devices: any[] = [];
        devices.forEach((item: any) => {
          _devices.push({deviceId: item.deviceId, kind: item.kind, label: item.label});
        })
        resolve(_devices);
      }, (err: any) => {
        reject(err);
      });
    })
  }
}

export default class AgoraWebClient {

  public readonly rtc: AgoraRTCClient;
  public shareClient: AgoraRTCClient | any;
  public localUid: number;
  public channel: string;
  public readonly bus: EventEmitter;
  public shared: boolean;
  public joined: boolean;
  public published: boolean;
  public tmpStream: any;

  constructor() {
    this.localUid = 0;
    this.channel = '';
    this.rtc = new AgoraRTCClient();
    this.bus = new EventEmitter();
    this.shared = false;
    this.shareClient = null;
    this.tmpStream = null;
    this.joined = false;
    this.published = false;
  }

  async getDevices () {
    const client = new AgoraRTCClient();
    await client.initClient(APP_ID);
    await client.createLocalStream({
      streamID: 0,
      audio: true,
      video: true,
      microphoneId: '',
      cameraId: ''
    });
    setTimeout(() => {
      client.destroyLocalStream();
    }, 80);
    return client.getDevices();
  }

  async joinChannel({
    uid, channel, dual, token
  }: {
    uid: number,
    channel: string,
    dual: boolean,
    token: string
  }) {
    this.localUid = uid;
    this.channel = channel;
    await this.rtc.createClient(APP_ID, true);
    await this.rtc.join(this.localUid, channel, token);
    dual && await this.rtc.enableDualStream();
    // console.log('555555555555555555555')
    // 改成手动发布
    // roomStore.setRTCJoined(true);
    this.joined = true;

  }
  // async publishStream() {
  //   if (this.joined) {
  //     return roomStore.setRTCJoined(true);
  //   }
  // }

  async leaveChannel() { console.log('unpublish leaveChannel' , 3);
    this.localUid = 0;
    this.channel = '';
    await this.unpublishLocalStream();
    await this.rtc.leave();
    this.rtc.destroy();
    this.rtc.destroyClient();
    this.joined = false;
    roomStore.setRTCJoined(false);
  }

  async enableDualStream() {
    return this.rtc.enableDualStream();
  }

  async publishLocalStream(data: AgoraStreamSpec) {
    console.log(" publish local stream ", roomStore.state.me.uid);
    if (this.published) {
      await this.unpublishLocalStream();
      console.log("[agora-web] unpublished", roomStore.state.me.uid);
    }
    await this.rtc.createLocalStream(data);
    await this.rtc.publish();
    this.published = true;
  }

  async unpublishLocalStream() { console.log('unpublish LocalStream' , 4);
    console.log("[agora-web] invoke unpublishStream");
    try{
      await this.rtc.unpublish();
    } catch (e) {

    }
    this.published = false;
  }

  async startScreenShare (token: string) {
    this.shareClient = new AgoraRTCClient();
    await this.shareClient.createLocalStream({
      video: false,
      audio: false,
      screen: true,
      screenAudio: true,
      streamID: SHARE_ID,
      microphoneId: '',
      cameraId: ''
    })
    await this.shareClient.createClient(APP_ID);
    await this.shareClient.join(SHARE_ID, this.channel, token);
    await this.shareClient.publish();
    this.shared = true;
  }

  async stopScreenShare () {
    await this.shareClient.unpublish();
    await this.shareClient.leave();
    await this.shareClient.destroy();
    await this.shareClient.destroyClient();
    roomStore.removeLocalSharedStream();
    this.shared = false;
  }

  async exit () {
    await this.leaveChannel();
    if (this.shared === true) {
      await this.shareClient.unpublish();
      await this.shareClient.leave();
    }
    if (this.shareClient) {
      await this.shareClient.destroy();
      await this.shareClient.destroyClient();
    }
  }

  async createPreviewStream({cameraId, microphoneId, speakerId}: any) {
    const tmpStream = AgoraRTC.createStream({
      video: true,
      audio: true,
      screen: false,
      cameraId,
      microphoneId,
      speakerId
    });

    if (this.tmpStream) {
      this.tmpStream.isPlaying() && this.tmpStream.stop();
      this.tmpStream.close();
    }

    return new Promise((resolve, reject) => {
      tmpStream.init(() => {
        this.tmpStream = tmpStream;
        resolve(tmpStream);
      }, (err: any) => {
        reject(err);
      })
    });
  }

  subscribe(stream: any) {
    this.rtc.subscribe(stream);
  }

  setRemoteVideoStreamType(stream: any, type: number) {
    this.rtc.setRemoteVideoStreamType(stream, type);
  }
}
