import {platform} from './../utils/platform';
import {AgoraElectronClient} from './../utils/agora-electron-client';
import {AgoraStream, ChatMessage, TOOL_TYPE} from '../utils/types';
import {Subject} from 'rxjs';
import {List, OrderedMap, OrderedSet} from 'immutable';
import AgoraRTMClient, {RoomMessage} from '../utils/agora-rtm-client';
import {globalStore} from './global';
import AgoraWebClient from '../utils/agora-rtc-client';
import {get} from 'lodash';
import {isElectron} from '../utils/platform';
import GlobalStorage from '../utils/custom-storage';
import {STAGE_NUM} from "../utils/consts";
// import {useLocation} from "react-router-dom";
import {getApiServerHost} from "../services/agora-end-points";

function canJoin({uid, onlineStatus, roomType, channelCount, role}: { uid: any, onlineStatus: any, role: string, channelCount: number, roomType: number}) {
  const result = {
    permitted: true,
    reason: ''
  }
  // const channelCountLimit = [2, 17, 17];
  //
  // let maximum = channelCountLimit[roomType];
  // if (channelCount >= maximum) {
  //   result.permitted = false;
  //   result.reason = 'The number of students and teacher have reached upper limit';
  //   return result;
  // }

  const teacher = get(onlineStatus, 'teacher', false);
  // const totalCount: number = get(onlineStatus, 'totalCount', 0);

  if (role === 'teacher') {
    const isOnline = teacher && `${uid}` !== `${onlineStatus.teacherId}`;
    if (isOnline) {
      result.permitted = false;
      result.reason = 'Teacher already existed';
      return result;
    }
  }

  // if (role === 'student') {
  //   if (totalCount+1 > maximum) {
  //     result.permitted = false;
  //     result.reason = 'Student have reached upper limit';
  //     return result;
  //   }
  // }

  return result;
}
const WaitStreamPool = new Map();
// const stageUpStudentTsMap: Map<number, number> = new Map();
const studentsOrderTs: number[] =  Array(STAGE_NUM).fill(0);

export enum RTC_STATUS{
  PEER_ONLINE, STREAM_PUBLISHED, STREAM_ADDED, STREAM_SUBSCRIBED, STREAM_REMOVED, PEER_LEAVE
}
const userStreamStatus: any = {};
export interface AgoraUser {
  uid: string
  account: string
  role: string
  video: number
  audio: number
  chat: number
  boardId: string // whiteboard_uuid
  sharedId: number // shared_uid
  linkId: number // link_uid
  lockBoard?: number // lock_board
  grantBoard: number
  lastStageTs: 0,
  stars: number

}

export interface ClassState {
  rid: string
  roomName: string
  teacherId: string
  roomType: number
  boardId: string // whiteboard_uuid
  sharedId: number // shared_uid
  linkId: number // link_uid
  lockBoard: number // lock_board
  courseState: number
  muteChat: number,
  currentTool: TOOL_TYPE,
  muteAudio: number,
}

type RtcState = {
  published: boolean
  joined: boolean
  users: OrderedSet<number>
  shared: boolean
  localStream: AgoraMediaStream | null
  localSharedStream: AgoraMediaStream | null
  remoteStreams: OrderedMap<number, AgoraMediaStream>
}

export type MediaDeviceState = {
  microphoneId: string
  speakerId: string
  cameraId: string
  speakerVolume: number
  camera: number
  microphone: number
  speaker: number
}

export type SessionInfo = {
  uid: string
  rid: string
  account: string
  roomName: string
  roomType: number
  role: string
}

export type RtmState = {
  joined: boolean
  memberCount: number
}
type userClassTimeType = {
  uid: number;
  in: number,
  out: number,
  all: number,
  role: string,
}
type userUsageType = {
  [key:string]: userClassTimeType
}
type userStarsType = {
  [key:string]: number
}
type handsUpUser = {
  [key:number]: any
}
export type RoomState = {
  rtmLock: boolean
  me: AgoraUser
  users: OrderedMap<string, AgoraUser>
  course: ClassState
  applyUid: number
  rtc: RtcState
  rtm: RtmState
  mediaDevice: MediaDeviceState
  messages: List<ChatMessage>
  studentsOrder: number[]
  userUsage: userUsageType,
  userStars: userStarsType,
  handsUpStudents: handsUpUser, // immuMap<number, any>
  cwLink: string,
}

export type AgoraMediaStream = {
  streamID: number
  stream?: any
}
interface  userStatus  extends AgoraUser{
  onStage: boolean
}
const _studentsStatus: {[key: number]: userStatus } = {};
let _sOrderStatus: number[]= [];




export class RoomStore {
  private subject: Subject<RoomState> | null;
  public _state: RoomState;
  private syncStudentsOrderTimer = null;
  private _sOrderStatus = _sOrderStatus;
  private _studentsStatus = _studentsStatus;

  get state () {
    return this._state;
  }

  set state (newState) {
    this._state = newState;
  }
  public rtmClient: AgoraRTMClient = new AgoraRTMClient();
  public rtcClient: AgoraWebClient | AgoraElectronClient = isElectron ? new AgoraElectronClient () : new AgoraWebClient();
  public readonly defaultState: RoomState = Object.freeze({
    rtmLock: false,
    me: {
      account: "",
      uid: "",
      role: "",
      video: 1,
      audio: 1,
      chat: 1,
      linkId: 0,
      sharedId: 0,
      boardId: '',
    },
    users: OrderedMap<string, AgoraUser>(),
    applyUid: 0,
    rtm: {
      joined: false,
      memberCount: 0,
    },
    rtc: {
      published: false,
      joined: false,
      shared: false,
      users: OrderedSet<number>(),
      localStream: null,
      localSharedStream: null,
      remoteStreams: OrderedMap<number, AgoraMediaStream>(),
    },
    course: {
      teacherId: '',
      boardId: '',
      sharedId: 0,
      linkId: 0,
      courseState: 0,
      muteChat: 0,
      muteAudio: 0,
      rid: '',
      roomName: '',
      roomType: 0,
      currentTool: TOOL_TYPE.WHITEBOARD
    },
    mediaDevice: {
      microphoneId: '',
      speakerId: '',
      cameraId: '',
      speakerVolume: 100,
      camera: 0,
      speaker: 0,
      microphone: 0
    },
    messages: List<ChatMessage>(),
    studentsOrder: [],
    userUsage: {},
    userStars:{},
    handsUpStudents: {}, // immuMap<number, any>(),
    ...GlobalStorage.read('agora_room')
  });

  private applyLock: number = 0;

  public windowId: number = 0;

  constructor() {
    this.subject = null;
    this._state = {
      ...this.defaultState
    };
    const webClient = this.rtcClient as AgoraWebClient;
    webClient.rtc.on('stream-reconnect-start', (evt: any) => {
      console.log('[agora-web] stream-reconnect-start', evt);
    });
    webClient.rtc.on('connection-state-change', (evt: any) => {
      console.log('[agora-web] connection-state-change', evt);
    });
    webClient.rtc.on('reconnect', (evt: any) => {
      console.log('[agora-web] reconnect', evt);
    });
    webClient.rtc.on('connected', (evt: any) => {
      console.log('[agora-web] connected', evt);
    });
    webClient.rtc.on('stream-fallback', (evt: any) => {
      console.log('[agora-web] stream-fallback', evt);
    });
    webClient.rtc.on('stream-updated', (evt: any) => {
      console.log('[agora-web] stream-updated', evt);
    });
    webClient.rtc.on('exception', (evt: any) => {
      console.log('[agora-web] exception', evt);
    });

    /*

    // const location = useLocation();
    webClient.rtc.on('onTokenPrivilegeWillExpire', (evt: any) => {
      // you need obtain the `newToken` token from server side
      const newToken = '';
      webClient.rtc.renewToken(newToken);
      console.log('[agora-web] onTokenPrivilegeWillExpire', evt);
    });
    webClient.rtc.on('onTokenPrivilegeDidExpire', (evt: any) => {
      // you need obtain the `newToken` token from server side
      const newToken = '';
      webClient.rtc.renewToken(newToken);
      console.log('[agora-web] onTokenPrivilegeDidExpire', evt);
    });
    webClient.rtc.on('error', (evt: any) => {
      console.log('[agora-web] error evt', evt);
    });
    webClient.rtc.on('stream-published', ({ stream }: any) => {
      const _stream = new AgoraStream(stream, stream.getId(), true);
      console.log('EVENT ON stream-published');
      roomStore.addLocalStream(_stream);
    });
    webClient.rtc.on('stream-subscribed', ({ stream }: any) => {
      console.log('stream', 2, 'stream-subscribed')
      const streamID = stream.getId();
      // when streamID is not share_id use switch high or low stream in dual stream mode
      if (streamID !== SHARE_ID) {
        if (roomStore.state.course.teacherId
          && roomStore.state.course.teacherId === `${streamID}`) {
          webClient.setRemoteVideoStreamType(stream, 0);
          console.log("[agora-web] dual stream set high for teacher");
        }
        else {
          webClient.setRemoteVideoStreamType(stream, 1);
          console.log("[agora-web] dual stream set low for student");
        }
      }
      console.log('stream step:', 3,'uid', streamID, 'addRemoteStream to play')
      const _stream = new AgoraStream(stream, streamID, false);
      console.log("[agora-web] subscribe remote stream, id: ", stream.getId());
      roomStore.addRemoteStream(_stream);
    });
    webClient.rtc.on('stream-added', ({ stream }: any) => {
      console.log('stream', 1, 'stream-added')
      console.log("[agora-web] added remote stream, id: ", stream.getId());
      webClient.subscribe(stream);

    });
    webClient.rtc.on('stream-removed', ({ stream }: any) => {
      console.log("[agora-web] removed remote stream, id: ", stream.getId(), roomStore.applyUid);
      const id = stream.getId();
      if (id === roomStore.applyUid) {
        globalStore.removeNotice();
        this.state.me.role === 'teacher' &&
        roomStore.updateCourseLinkUid(0).then(() => {
          console.log("update teacher link_uid to 0");
        }).catch(console.warn);
      }
      roomStore.removeRemoteStream(stream.getId());
    });
    webClient.rtc.on('peer-online', ({uid}: any) => {
      console.log("[agora-web] peer-online, id: ", uid);
      roomStore.addPeerUser(uid);
    });
    webClient.rtc.on('peer-leave', ({ uid }: any) => {
      console.log("[agora-web] peer-leave, id: ", uid, roomStore.applyUid);
      if (uid === roomStore.applyUid) {
        globalStore.removeNotice();
        this.state.me.role === 'teacher' &&
        roomStore.updateCourseLinkUid(0).then(() => {
          console.log("update teacher link_uid to 0");
        }).catch(console.warn);
      }
      roomStore.removePeerUser(uid);
      roomStore.removeRemoteStream(uid);
    });
    webClient.rtc.on("stream-fallback", ({ uid, attr }: any) => {
      const msg = attr === 0 ? 'resume to a&v mode' : 'fallback to audio mode';
      console.info(`[agora-web] stream: ${uid} fallback: ${msg}`);
    })*/
    // @ts-ignore
    window['roomStore'] = this;
  }


  async addStarToUser(uid: number, value: number) {
    if (value <= 0) {
      return;
    }
    const data = {star: value, users: []};
    console.log('addStarToUser', uid, value);
    const stuChannelData: any = {};
    const userStars = {...this.state.userStars};
    if (uid !== 0) {
      const stu = this.state.users.get(`${uid}`);
      (data.users as number[]).push(uid)
      let uStar = 0;
      if (stu) {
        uStar = userStars[`${uid}`];//stu.stars;
        if (!uStar || isNaN(uStar)) {
          uStar = 0;
        }
        // stu.stars = uStar + value;
        userStars[`${uid}`] = uStar + value;
        stuChannelData[uid] = stu

      }

    } else {
      this.state.users.forEach((u, _uid) => {
        if (u.role === 'student') {
          (data.users as number[]).push(+_uid);
          let uStar = userStars[`${_uid}`];//stu.stars;
          if (!uStar || isNaN(uStar)) {
            uStar = 0;
          }
          // u.stars = u.stars + value;
          userStars[`${_uid}`] = uStar + value;
          stuChannelData[_uid] = u
        }
      })
    }
    try{
      const host = getApiServerHost(true);
      var blob = new Blob([JSON.stringify(data)], {
        type: 'text/plain'
      });
      navigator.sendBeacon(`${host}/api/classroom/stars`, blob);
    } catch(e){}


    // this.state = {
    //   ...this.state,
    //   userStars
    // }
    console.log(stuChannelData)
    if (Object.keys(stuChannelData).length > 0) {
      stuChannelData['userStars'] = userStars;
      await this.rtmClient.addOrUpdateChannelAttributes(stuChannelData);
    }
  }
  async cancelHandsUp(uid: number) {

    let handle = this.state.handsUpStudents[uid];
    if (handle) {
      clearTimeout(handle);
      handle = null;
      const hus = {...this.state.handsUpStudents}
      delete hus[uid];
      // this.state.handsUpStudents.set(uid, null);
      this.state = {
        ...this.state,
        handsUpStudents: hus,
      };
      await this.commit(this.state);
    }
  }

  async studentHandsUp(uid: number) {
    // const hu = this.state.handsUpStudents[uid];
    // !hu && !this.state.studentsOrder.includes(uid)
      const handleId = setTimeout(() => {
        this.cancelHandsUp(uid);
      }, 8000);
      const hus = {...this.state.handsUpStudents}
      hus[uid] = handleId;
      this.state = {
        ...this.state,
        handsUpStudents: hus,
      };
      await this.commit(this.state);
  }
  testStudentsOrder() {
    console.log('studentsOrder', this.state.studentsOrder);
    console.log('studentsOrderTs', studentsOrderTs);
  }
  async stageUp(uid: string) {

    let studentsOrder: any[] = [...this.state.studentsOrder];
    if (studentsOrder.indexOf(+uid) > -1) {
      return;
    }
    const data: any = {};
    const emptyIndex = studentsOrder.indexOf(0);
    let orderIndex = -1;
    if (emptyIndex > -1 && !studentsOrder.includes(uid)) {
      orderIndex = emptyIndex;

    } else {
      const oldestTs = Math.min(...studentsOrderTs);
      orderIndex = studentsOrderTs.indexOf(oldestTs);
    }
    if (studentsOrder[orderIndex] === +uid) {
      return ;
    }


    studentsOrder[orderIndex] = +uid;
    studentsOrderTs[+orderIndex] = Date.now()
    const upUser: AgoraUser | undefined = this.state.users.get(`${uid}`);
    if (upUser) {
      upUser.video = 1;
      upUser.audio = 1;
      // @ts-ignore
      upUser.last_stage_ts = Date.now();
      data[upUser.uid] = upUser;
    }

    this.state = {
      ...this.state,
      studentsOrder
    }
    await this.commit(this.state);
    this.cancelHandsUp(+uid);
    this.releaseWaitStreamHandle(+uid);
    data['studentsOrder'] = studentsOrder;
    await this.rtmClient.addOrUpdateChannelAttributes(data);
  }
  async stageDown(uid: string) {

    //把台上位置变零
    let studentsOrder: any[] = [...this.state.studentsOrder];
    console.log('下台', uid, studentsOrder)
    const uidIndex = studentsOrder.indexOf(+uid)
    if (uidIndex > -1) {
      studentsOrderTs[+uidIndex] = 0
      studentsOrder.splice(uidIndex, 1, 0);
      console.log('下台后', uid, studentsOrder)
      const data: any = {};
      data['studentsOrder'] = studentsOrder;
      const downUser: AgoraUser | undefined = this.state.users.get(`${uid}`);
      if (downUser) {
        downUser.audio = 0;
        downUser.video = 0;
        // @ts-ignore
        downUser.last_stage_ts = 0;
        data[uid] = downUser
      }
      this.state = {
        ...this.state,
        studentsOrder
      }
      await this.commit(this.state);
      await this.rtmClient.addOrUpdateChannelAttributes(data);
    }


  }

  async randomStageUp() {
    const users = roomStore.state.users;
    const studentsOrder = roomStore.state.studentsOrder || [];

    const downUser: any = [];
    users.forEach((u, uid) => {
      if (`${u.uid}` !== `${this.state.course.teacherId}` && !studentsOrder.includes(+u.uid)) {
        downUser.push(u);
      }
    })
    const upUser = downUser[Math.floor(Math.random() * downUser.length)]
    // console.log('upUser', upUser)
    if (upUser) {
      this.stageUp(`${upUser.uid}`)
    }
  }

  async setCurrentTool (tool: TOOL_TYPE) {
    this.state = {
      ...this.state,
      course: {
        ...this.state.course,
        currentTool: tool
      },
    }

    const me = this.state.me;
    let res = await this.updateAttrsBy(me.uid, {
      current_tool: tool
    }).then(() => {
      console.log("update success");
    }).catch(console.warn)

    // console.log("[update tool uid] res", uid);
    return res;
  }
  initialize() {
    this.subject = new Subject<RoomState>();
    this.state = {
      ...this.defaultState,
    }
    this.applyLock = 0;
    this.subject.next(this.state);
  }

  get applyUid () {
    return this.applyLock;
  }

  subscribe(updateState: any) {
    this.initialize();
    this.subject && this.subject.subscribe(updateState);
  }

  unsubscribe() {
    this.subject && this.subject.unsubscribe();
    this.subject = null;
  }

  async commit (state: RoomState) {
    // console.log('state changed', state);

    return this.subject && this.subject.next(state);
  }

  updateState(rootState: RoomState) {
    this.state = {
      ...this.state,
      ...rootState,
    }
    this.commit(this.state);
  }

  isTeacher(peerId: string) {
    if (!peerId) return false;
    const user = this.state.users.get(peerId)
    if (!user) return false;
    if (user.role === 'teacher') return true;
    return false;
  }

  isStudent (peerId: string) {
    if (!peerId) return false;
    const user = this.state.users.get(peerId);
    if (!user) return false;
    if (user.role === 'student') return true;
    return false;
  }

  async addLocalStream(stream: AgoraStream) {

    this.state = {
      ...this.state,
      rtc: {
        ...this.state.rtc,
        localStream: stream
      }
    }
    await this.commit(this.state);
  }

  async removeLocalStream() {
    this.state = {
      ...this.state,
      rtc: {
        ...this.state.rtc,
        localStream: null,
        localSharedStream: null
      }
    }
    await this.commit(this.state);
  }

  async addLocalSharedStream(stream: any) {
    this.state = {
      ...this.state,
      rtc: {
        ...this.state.rtc,
        localSharedStream: stream
      }
    }
    await this.commit(this.state);
  }

  async removeLocalSharedStream() {
    this.state = {
      ...this.state,
      rtc: {
        ...this.state.rtc,
        localSharedStream: null
      }
    }
    await this.commit(this.state);
  }
  getUserUsage() {
    // console.log(this.state.userUsage);
    return this.state.userUsage;


    // const host = getApiServerHost();
    // data = [{uid: 1, time: 50}, {uid: 2, time: 5}]
    // const url = `${host}/live_management/api/classroom/finish`;
    // const blob = new Blob([JSON.stringify(data)], {
    //   type: 'text/plain'
    // });
    // const r = navigator.sendBeacon(url, blob);

    // fetch(url, {
    //   method: "POST",
    //   body: JSON.stringify(data),
    //   headers: {
    //     'Content-Type': 'application/json',
    //   },
    //   credentials: 'include',
    //   mode: 'no-cors', // no-cors
    //   keepalive: true,
    // })

    // console.log(r);
    // var xhr = new XMLHttpRequest();
    // xhr.open("POST", `${host}/api/classroom/finish`, true); // third parameter of `false` means synchronous
    // xhr.send(JSON.stringify(data));


  }
  _userIn(uid: number, role = 'student') {
    if (role !== 'teacher' &&  this.state.me.role !== 'teacher') {
      return;
    }
    const currUser: AgoraUser | undefined = this.state.users.get(`${uid}`);
    if (currUser) {
      role = currUser.role;
    }

    console.log('userusage _userIn', uid);
    const uc = this.state.userUsage[`${uid}`];
    const now = Date.now();
    if (uc) {
      if (uc.in <= uc.out) {
        uc.in = now;
        uc.out = 0;
      }
    } else {
      this.state.userUsage[`${uid}`] = {
        uid,
        role,
        in: now,
        out: 0,
        all: 0
      }
    }
  }
  _userOut(uid: number) {
    if (this.state.me.role !== 'teacher') {
      return;
    }
    console.log('userusage _userOut', uid);
    const uc = this.state.userUsage[`${uid}`];
    const now = Date.now();
    if (uc) {
      uc.out = now;
      if (uc.in < uc.out) {
        let ts = 0;
        if (uc.in !== 0) {
          ts = uc.out - uc.in;;
        }
        if (!uc.all) {
          uc.all = 0;
        }
        uc.all += ts;
        uc.in = 0;
        uc.out = 0;
      }
    }
  }
  async addPeerUser(uid: number) {
    if (!uid) {
      return;
    }
    // const userStars = {...this.state.userStars}
    // if (!userStars[uid]) {
    //   userStars[uid] = 0;
    // }
    this.setUserStreamStatus(uid, RTC_STATUS.PEER_ONLINE);

    this.state = {
      ...this.state,
      rtc: {
        ...this.state.rtc,
        users: this.state.rtc.users.add(uid),
      },
      // userStars
    }


    await this.commit(this.state);
  }

  async removePeerUser(uid: number) {
    if (!uid) {
      return;
    }
    this.deleteUserStreamStatus(uid)
    // 如果一个流退了，检测是否是台上人员，移除掉,其实就是下台
    // await this.stageDown(`${uid}`)
    this.releaseWaitStreamHandle(uid);
    const orders = [...this.state.studentsOrder];
    const checkIdx1 = orders.indexOf(+uid); // 在台上
    const checkIdx2 = orders.indexOf(-uid); // 等待中
    if (checkIdx1 > 0) {
      orders.splice(checkIdx1, 1, 0)
      studentsOrderTs[checkIdx1] = 0;
    }
    if (checkIdx2 > 0) {
      orders.splice(checkIdx2, 1, 0);
      studentsOrderTs[checkIdx2] = 0;
    }

    const remoteStream = this.state.rtc.remoteStreams.get(uid);
    if (platform === 'web') {
      if (remoteStream && remoteStream.stream && remoteStream.stream.isPlaying) {
        remoteStream.stream.isPlaying() && remoteStream.stream.stop();
      }
    }

    if (checkIdx1 >= 0 || checkIdx2 >= 0) {
      await this.rtmClient.addOrUpdateChannelAttributes({studentsOrder: orders});
    }


    this.state = {
      ...this.state,
      rtc: {
        ...this.state.rtc,
        users: this.state.rtc.users.delete(uid),
      },
      studentsOrder: orders
    }
    await this.commit(this.state);
  }

  setUserStreamStatus(uid: number, status: RTC_STATUS){
    userStreamStatus[uid] = status;
  }
  getUserStreamStatus(uid: number | undefined) {
    if (uid) {
      return userStreamStatus[uid];
    }
    return userStreamStatus;
  }
  deleteUserStreamStatus(uid: number) {
    return delete userStreamStatus[uid];
  }

  async addRemoteStream(stream: AgoraStream) {
    // const sod = this.state.studentsOrder;
    // const hasMe = sod.indexOf(stream.streamID);
    // if (!hasMe && stream.streamID > 0) {
    //   const emptyIndex = sod.indexOf(0);
    //   sod.splice(emptyIndex, 1, stream.streamID)
    // }
    roomStore.setUserStreamStatus(stream.streamID, RTC_STATUS.STREAM_SUBSCRIBED)

    console.log('remoteStreams added',5 , stream.streamID, this.state.users)
    this.state = {
      ...this.state,
      rtc: {
        ...this.state.rtc,
        remoteStreams: this.state.rtc.remoteStreams.set(stream.streamID, stream)
      },
      // studentsOrder: sod
    }
    await this.commit(this.state);
    if (stream.streamID > 0 && +stream.streamID !== +this.state.course.teacherId) {
      this.stageUp(''+stream.streamID)
    }
  }

  async removeRemoteStream(uid: number) {
    this.setUserStreamStatus(uid, RTC_STATUS.STREAM_REMOVED)
    this.state = {
      ...this.state,
      rtc: {
        ...this.state.rtc,
        remoteStreams: this.state.rtc.remoteStreams.delete(uid)
      },

    }
    await this.commit(this.state);
  }

  async updateMemberCount(count: number) {
    // console.log('updateMemberCount1', this.state.studentsOrder);
    this.state = {
      ...this.state,
      rtm: {
        ...this.state.rtm,
        memberCount: count,
      }
    }
    await this.commit(this.state);
    // console.log('updateMemberCount2', this.state.studentsOrder);
  }

  updateRtc(newState: any) {
    this.state = {
      ...this.state,
      rtc: {
        ...this.state.rtc,
        ...newState,
      }
    }
    this.commit(this.state);
  }

  updateDevice(state: MediaDeviceState) {
    this.state = {
      ...this.state,
      mediaDevice: state
    }
    this.commit(this.state);
  }

  async handlePeerMessage(cmd: RoomMessage, peerId: string) {
    if (!peerId) return console.warn('state is not assigned');
    const myUid = this.state.me.uid;
    console.log("Teacher: ", this.isTeacher(myUid) , ", peerId: ", this.isStudent(peerId), cmd);
    // student follow teacher peer message
    if (!this.isTeacher(myUid) && this.isTeacher(peerId)) {

      const me = this.state.me;
      switch(cmd) {
        case RoomMessage.muteChat: {
          return await this.updateMe({...me, chat: 0});
        }
        case RoomMessage.muteAudio: {
          return await this.updateMe({...me, audio: 0});
        }
        case RoomMessage.muteVideo: {
          return await this.updateMe({...me, video: 0});
        }
        case RoomMessage.muteBoard: {
          globalStore.showToast({
            message: `Teacher already cancel your whiteboard`,
            type: 'notice'
          });
          return await this.updateMe({...me, grantBoard: 0});
        }
        case RoomMessage.unmuteAudio: {
          return await this.updateMe({...me, audio: 1});
        }
        case RoomMessage.unmuteVideo: {
          return await this.updateMe({...me, video: 1});
        }
        case RoomMessage.unmuteChat: {
          return await this.updateMe({...me, chat: 1});
        }
        case RoomMessage.unmuteBoard: {
          globalStore.showToast({
            message: `Teacher already permit your whiteboard`,
            type: 'notice'
          });
          return await this.updateMe({...me, grantBoard: 1});
        }
        case RoomMessage.acceptCoVideo: {
          globalStore.showToast({
            type: 'co-video',
            message: 'teacher already accept co-video'
          });
          return;
        }
        case RoomMessage.rejectCoVideo: {
          globalStore.showToast({
            type: 'co-video',
            message: 'teacher already rejected co-video'
          });
          return;
        }
        case RoomMessage.cancelCoVideo: {
          globalStore.showToast({
            type: 'co-video',
            message: 'teacher already canceled co-video'
          });
          return;
        }

        default:
      }
      return;
    }

    // when i m teacher & received student message
    if (this.isTeacher(myUid) && this.isStudent(peerId)) {
      switch(cmd) {
        case RoomMessage.stageUp: {
          this.stageUp(peerId)
          return
        }
        case RoomMessage.stageDown: {
          this.stageDown(peerId);
          return
        }
        case RoomMessage.handsUp: {
          this.studentHandsUp(+peerId);
          return;
        }
        case RoomMessage.applyCoVideo: {
          // WARN: LOCK
          if (this.state.course.linkId) {
            return console.warn('already received apply id: ', this.applyLock);
          }
          const applyUser = roomStore.state.users.get(`${peerId}`);
          if (applyUser) {
            this.applyLock = +peerId;
            console.log("applyUid: ", this.applyLock);
            this.state = {
              ...this.state,
              applyUid: this.applyLock,
            }
            this.commit(this.state);
            globalStore.showNotice({
              reason: 'peer_hands_up',
              text: `"${applyUser.account}" wants to interact with you`
            });
          }
          return;
        }
        case RoomMessage.cancelCoVideo: {
          // WARN: LOCK
          if (this.state.course.linkId && `${this.state.course.linkId}` === peerId) {
            roomStore.updateCourseLinkUid(0).then(() => {
            }).catch(console.warn);

            globalStore.showToast({
              type: 'co-video',
              message: 'student canceled co-video'
            });
          }
          return;
        }
        default:
      }
      return;
    }
  }

  async mute(uid: string, type: string) {
    const me = this.state.me;
    console.log(">>>>> mute, uid, ", uid, " type: ", type);
    if (me.uid === `${uid}`) {
      if (type === 'audio') {
        await this.updateAttrsBy(me.uid, {
          audio: 0
        });
      }
      if (type === 'video') {
        await this.updateAttrsBy(me.uid, {
          video: 0
        });
      }
      if (type === 'chat') {
        await this.updateAttrsBy(me.uid, {
          chat: 0
        });
      }
      // if (type === 'grantBoard') {
      //   await this.updateAttrsBy(me.uid, {
      //     grant_board: 0
      //   });
      // }
    }
    else if (me.role === 'teacher') {
      if (type === 'audio') {
        await this.rtmClient.sendPeerMessage(`${uid}`, {cmd: RoomMessage.muteAudio});
      }
      if (type === 'video') {
        await this.rtmClient.sendPeerMessage(`${uid}`, {cmd: RoomMessage.muteVideo});
      }
      if (type === 'chat') {
        await this.rtmClient.sendPeerMessage(`${uid}`, {cmd: RoomMessage.muteChat});
      }
      if (type === 'grantBoard') {
        await this.rtmClient.sendPeerMessage(`${uid}`, {cmd: RoomMessage.muteBoard});
      }
    }
  }

  async unmute(uid: string, type: string) {
    // 有一个unmute的时候，就把course里的muteAudio 变成0
    if (this.state.me.role === 'teacher') {
      this.state = {
        ...this.state,
        course: {
          ...this.state.course,
          muteAudio: 0
        }
      }
      this.commit(this.state)
    }
    const me = this.state.me;
    if (me.uid === `${uid}`) {
      if (type === 'audio') {
        await this.updateAttrsBy(me.uid, {
          audio: 1
        });
      }
      if (type === 'video') {
        await this.updateAttrsBy(me.uid, {
          video: 1
        });
      }
      if (type === 'chat') {
        await this.updateAttrsBy(me.uid, {
          chat: 1
        });
      }
      // if (type === 'grantBoard') {
      //   await this.updateAttrsBy(me.uid, {
      //     grant_board: 1
      //   });
      // }
    }
    else if (me.role === 'teacher') {
      if (type === 'audio') {
        await this.rtmClient.sendPeerMessage(`${uid}`, {cmd: RoomMessage.unmuteAudio});
      }
      if (type === 'video') {
        await this.rtmClient.sendPeerMessage(`${uid}`, {cmd: RoomMessage.unmuteVideo});
      }
      if (type === 'chat') {
        await this.rtmClient.sendPeerMessage(`${uid}`, {cmd: RoomMessage.unmuteChat});
      }
      if (type === 'grantBoard') {
        await this.rtmClient.sendPeerMessage(`${uid}`, {cmd: RoomMessage.unmuteBoard});
      }
    }
  }

  async loginAndJoin(payload: any, pass: boolean = false) {

    // payload.rid = `channel_${payload.rid}`
    console.log('channel:', payload.rid);
    const {roomType, role, uid, rid, token} = payload;
    try{await this.rtmClient.logout();} catch(e){}

    await this.rtmClient.login(uid, token);

    const channelMemberCount = await this.rtmClient.getChannelMemberCount([rid]);
    const channelCount = channelMemberCount[rid];
    const channelMeta = await this.rtmClient.getChannelAttributeBy(rid);
    console.log('channelMeta', channelMeta)
    if (payload.role === 'student' && !channelMeta.teacher) {
      // eslint-disable-next-line
      throw {
        type: 'not_permitted',
        reason: 'classroom not start yet'
      }
    }
    let accounts = channelMeta.accounts;
    const onlineStatus = await this.rtmClient.queryOnlineStatusBy(accounts);

    /*
    const me = accounts.find((o)=>{return `${payload.uid}` === `${o.uid}`});
    if (onlineStatus[payload.uid]) {
      payload.audio = me.audio;
      payload.video = me.video;

    } else {
      if (accounts.length >= STAGE_NUM) {
        payload.audio = 0;
        payload.video = 0;
        // payload.stage = 0;
      } else {
        // payload.stage = 1;
        payload.audio = 0;
        payload.video = 1;
      }
    }*/
    // for default
    payload.audio = 0;
    payload.video = 0;
    if (role === 'teacher') {
      payload.audio = channelMeta['teacher'] ? channelMeta['teacher'].audio : 1;
      payload.video = channelMeta['teacher'] ? channelMeta['teacher'].video : 1;
    }

    const argsJoin = {
      uid,
      channelCount,
      onlineStatus,
      role,
      accounts,
      roomType
    };
    let result = pass === false ? canJoin(argsJoin) : {permitted: true, reason: ''};

    let studentsOrder: number[] = channelMeta.studentsOrder || [];
    if (result.permitted) {
      if (payload.role === 'teacher') {

        // const t = this.state.userUsage[payload.uid];
        this._userIn(payload.uid, 'teacher')


        // studentsOrder = channelMeta.studentsOrder || [];//this.state.studentsOrder;
        if (studentsOrder.length < STAGE_NUM) {
          Array(STAGE_NUM - studentsOrder.length).fill(0).forEach((zero) => {
            studentsOrder.push(zero)
          })
        }
      } else {
        //查看有无空闲
        /*if (!studentsOrder.includes(+payload.uid)) {
          const emptyIndex = studentsOrder.indexOf(0);
          if (emptyIndex > -1) {
            studentsOrder.splice(emptyIndex, 1, +payload.uid);
          }
        }


        */
      }
      console.log('login accounts', accounts)
      let onlineMe = accounts.find((it: any) => `${it.uid}` === `${payload.uid}`);
      if (onlineMe) {
        payload.video = onlineMe.video;
        payload.audio = onlineMe.audio;
      } else {
        if (studentsOrder && studentsOrder.includes(+payload.uid)) {
          payload.video = 1;
          payload.audio = 1;
          const idx = studentsOrder.indexOf(+payload.uid);
          studentsOrderTs[idx] = Date.now();
        }
      }
      console.log('login payload', payload)
      console.log('login studentsOrder', studentsOrder)

      await this.rtmClient.join(rid).then(async r => {
        this.state = {
          ...this.state,
          rtm: {
            ...this.state.rtm,
            joined: true
          }
        }
        const grantBoard = role === 'teacher' ? 1 : 0;
        console.log(">>>>>>>>>>#room: ", grantBoard,this.state);
        await this.updateMe({...payload, grantBoard}, studentsOrder);
        this.commit(this.state);
      });
      // this.state = {
      //   ...this.state,
      //   rtm: {
      //     ...this.state.rtm,
      //     joined: true
      //   }
      // }
      // const grantBoard = role === 'teacher' ? 1 : 0;
      // console.log(">>>>>>>>>>#room: ", grantBoard);
      // await this.updateMe({...payload, grantBoard});
      // this.commit(this.state);
      return;
    } else {
      await this.rtmClient.logout();
      // eslint-disable-next-line
      throw {
        type: 'not_permitted',
        reason: result.reason
      }
    }

  }

  publishMeStream() {
    console.log(3, 'set rtc join flag, should auto publish', this.state.rtc.joined)
    // return await (this.rtcClient as AgoraWebClient).publishStream()
    // 通过index.tsx里的监听来触发发布
    if (!this.state.rtc.joined) {
      this.setRTCJoined(true);
    }

  }
  unPublishMeStream() {
    console.log(3, 'set rtc join flag false, should unPublishMeStream')
    // return await (this.rtcClient as AgoraWebClient).publishStream()
    if (this.state.rtc.joined) {
      this.setRTCJoined(false);
    }
  }

  setRTCJoined(joined: boolean) {
    console.log('setRTCJoined', joined);
    this.state = {
      ...this.state,
      rtc: {
        ...this.state.rtc,
        joined
      }
    }
    this.commit(this.state);
  }
  async leaveStage(uid: number) {
    const sOrders =  [...this.state.studentsOrder];
    const idx = sOrders.indexOf(+uid)
    const newInfo: any = {};
    if (idx > -1) {
      sOrders.splice(idx, 1, 0);
      studentsOrderTs[idx] = 0;
      this.state = {
        ...this.state,
        studentsOrder: sOrders
      }
      newInfo['studentsOrder'] = sOrders;
      await this.rtmClient.addOrUpdateChannelAttributes(newInfo);
    }
  }

  async updateCourseLinkUid(linkId: number) {
    const me = this.state.me;
    console.log("me: link_uid", me, linkId);
    let res = await this.updateAttrsBy(me.uid, {
      link_uid: linkId
    })
    // let res = await this.updateMe({...me, linkId: linkId});
    this.applyLock = linkId;
    console.log("current apply lock: ", this.applyLock);
    return res;
  }

  async updateWhiteboardUid(uid: string) {
    const me = this.state.me;
    let res = await this.updateAttrsBy(me.uid, {
      whiteboard_uid: uid
    });
    console.log("[update whiteboard uid] res", uid);
    return res;
  }

  updateChannelMessage(msg: ChatMessage) {
    this.state = {
      ...this.state,
      messages: this.state.messages.push(msg)
    };

    this.commit(this.state);
  }

  async updateAttrsBy(uid: string, attr: any) {
    if (attr['whiteboard_uid']) {
      console.log("[update whiteboard uid], ", attr['whiteboard_uid']);
    }
    const user = this.state.users.get(uid);
    if (!user) return;
    const key = user.role === 'teacher' ? 'teacher' : uid;
    const attrs = {
      uid: user.uid,
      whiteboard_uid: user.boardId,
      link_uid: user.linkId,
      shared_uid: user.sharedId,
      account: user.account,
      video: user.video,
      audio: user.audio,
      chat: user.chat,
      grant_board: user.grantBoard,
      last_stage_ts: user.lastStageTs,
      stars: user.stars || 0
    }
    if (user.role === 'teacher') {
      Object.assign(attrs, {
        mute_chat: this.state.course.muteChat,
        mute_audio: this.state.course.muteAudio,
        class_state: this.state.course.courseState,
        whiteboard_uid: this.state.course.boardId,
        link_uid: this.state.course.linkId,
        shared_uid: this.state.course.sharedId,
        current_tool: this.state.course.currentTool,
      })
    }
    if (attr) {
      Object.assign(attrs, attr);
    }
    const data: any = {};
    data[key] = attrs;
    // await this.rtmClient.addOrUpdateChannelAttributes(data);
    await this.rtmClient.updateChannelAttrsByKey(key, attrs);
    // return res;
  }
  async allStudentMuteOn(){
    const channelMeta = await this.rtmClient.getChannelAttributeBy(this.state.course.rid);
    let accounts = channelMeta.accounts;
    const data: any = {};
    accounts.forEach((acc: any) => {
      acc.audio = 0;
      data[acc.uid] =  acc;
    })
    // channelMeta.teacher.mute_audio = 0;
    // data['teacher'] = channelMeta.teacher;
    this.state = {
      ...this.state,
      course: {
        ...this.state.course,
        muteAudio: 1
      },
    }
    this.commit(this.state);
    if (Object.keys(data).length) {
      await this.rtmClient.addOrUpdateChannelAttributes(data);
    }
  }
  async allStudentMuteOff(){
    const channelMeta = await this.rtmClient.getChannelAttributeBy(this.state.course.rid);
    let accounts = channelMeta.accounts;
    const data: any = {};
    accounts.forEach((acc: any) => {
      acc.audio = 1;
      data[acc.uid] =  acc;
    })
    // channelMeta.teacher.mute_audio = 1;
    // data['teacher'] = channelMeta.teacher;
    this.state = {
      ...this.state,
      course: {
        ...this.state.course,
        muteAudio: 0
      },
    }
    this.commit(this.state);
    if (Object.keys(data).length) {
      await this.rtmClient.addOrUpdateChannelAttributes(data);
    }
  }
  async _removeWaitingStream(streamId: number) {
    console.log('_removeWaitingStream', streamId);

    let handle = WaitStreamPool.get(streamId);
    clearTimeout(handle);
    handle = null;
    WaitStreamPool.set(streamId, null);
    WaitStreamPool.delete(streamId);
    // 从orders里移除，那是直接移除orders还是直接删除？
    // 先移除orders吧
    // const newInfo: any = await this.syncStageStudentByTeacherOrder2(users.toArray(), currentRoomUsers);
    // console.log('newInfo', newInfo)
    // if (newInfo) {
    //   await this.rtmClient.clearChannelAttributesByKeys([`${streamId}`]);
    // }
    if (streamId < 0) {
      streamId = Math.abs(streamId)
    }

    if (streamId && `${streamId}` !== `${this.state.course.teacherId}`) {
      // console.log('remove user from channel', streamId);
      // await this.rtmClient.clearChannelAttributesByKeys([`${streamId}`]);
      // 这里不应该移除直播间，应该从rtc.users移除
      // 因为，从频道里移出去后，app没做相应处理，就会教师看不到他，而他还在直播间内
      this.state = {
        ...this.state,
        rtc: {
          ...this.state.rtc,
          users: this.state.rtc.users.delete(streamId),
        },
      }
      await this.commit(this.state);
      this.stageDown(`${streamId}`)
    }

    // this.stageUp(`${streamId}`)

  }
  async releaseWaitStreamHandle(streamId: number) {
    let handle = WaitStreamPool.get(streamId);
    if (handle) {
      console.log('releaseWaitStreamHandle has handle', streamId);
      let handle = WaitStreamPool.get(streamId);
      clearTimeout(handle);
      handle = null;
      WaitStreamPool.set(streamId, null);
      WaitStreamPool.delete(streamId);
    }
  }
  async lockWaitStreamHandle(streamId: number) {
    if (this.state.me.role !== 'teacher') {
      return
    }
    // console.log('waitStreamHandle', streamId);
    let handle = WaitStreamPool.get(streamId);
    console.log('waitStreamHandle', handle);
    if (handle) {
      console.log('waitStreamHandle has handle', streamId);
      return
      // this._removeWaitingStream(streamId);
    } else {
      console.log('waitStreamHandle add new handle', streamId);
      handle = setTimeout(() => {
        this._removeWaitingStream(streamId);
      }, 12 * 1000);
      WaitStreamPool.set(streamId, handle);
    }

  }

  async updateMe(user: any, studentsOrder: number[] = []) {
    let {role, uid, account, /*rid,*/ video, audio, chat, boardId, linkId, sharedId, /*muteChat, muteAudio,*/ grantBoard, stars} = user;
    const key = role === 'teacher' ? 'teacher' : uid;
    const me = this.state.me;
    const attrs = {
      uid: me.uid,
      account: me.account,
      chat: me.chat,
      video: me.video,
      audio: me.audio,
    }
    stars = stars || 0;
    Object.assign(attrs, {
      uid,
      account,
      video,
      audio,
      chat,
      whiteboard_uid: boardId,
      link_uid: linkId,
      shared_uid: sharedId,
      grant_board: grantBoard,
      last_stage_ts: Date.now(),
      stars
    });

    console.log('updateMe in', user);
    if (grantBoard !== undefined) {
      Object.assign(attrs, {
        grant_board: grantBoard,
      });
    }
    if (role === 'teacher') {
      const class_state = get(user, 'courseState', this.state.course.courseState);
      const whiteboard_uid = get(user, 'boardId', this.state.course.boardId);
      const mute_chat = get(user, 'muteChat', this.state.course.muteChat);
      const mute_audio = get(user, 'muteAudio', this.state.course.muteAudio);
      const shared_uid = get(user, 'sharedId', this.state.course.sharedId);
      const link_uid = get(user, 'linkId', this.state.course.linkId);
      const lock_board = get(user, 'lockBoard', this.state.course.lockBoard);
      const current_tool = get(user, 'currentTool', this.state.course.currentTool);

      Object.assign(attrs, {
        mute_audio,
        mute_chat,
        class_state,
        whiteboard_uid,
        link_uid,
        shared_uid,
        lock_board,
        current_tool
      })
      console.log("teacher attrs: >>>> ", attrs);
    }
    const data: any = {};
    data[key] = attrs;
    if (studentsOrder && studentsOrder.length) {
      data['studentsOrder'] = studentsOrder;
    }
    console.log('update me', data);
    await this.rtmClient.addOrUpdateChannelAttributes(data);
    // let res = await this.rtmClient.updateChannelAttrsByKey(key, attrs);
    // return res;
  }
  async rtmLeaveUser(memberId: string) {
    console.log('学生退出', memberId, this.state.studentsOrder);
    this._userOut(+memberId);
    const studentsOrder = [...this.state.studentsOrder];
    const userIndex = studentsOrder.indexOf(+memberId);
    // let rtc = {
    //   ...this.state.rtc,
    // };
    if (userIndex > -1) {
      studentsOrder.splice(userIndex, 1, 0);
      studentsOrder[userIndex] = 0;

      // rtc = {
      //   ...this.state.rtc,
      //   users: this.state.rtc.users.delete(+memberId),
      // };

      /*this.state = {
        ...this.state,
        rtc: {
          ...this.state.rtc,
          users: this.state.rtc.users.delete(+memberId),
        },
        users: this.state.users.delete(`${memberId}`)
      }*/

      this.state = {
        ...this.state,
        /*rtc: {
          ...this.state.rtc,
          users: this.state.rtc.users.delete(+memberId),
        },*/
        studentsOrder
      }

      // await this.rtmClient.addOrUpdateChannelAttributes(data);
      // await this.rtmClient.clearChannelAttributesByKeys([`${memberId}`]);
    }
    this.state = {
      ...this.state,
      users: this.state.users.delete(`${memberId}`)
    }
    await this.commit(this.state);

    const data: any  = {};
    data['studentsOrder'] = studentsOrder;
    console.log('rtmLeaveUser', data);

  }
  async rtmJoinUser(memberId: string) {
    console.log('新学生登录', memberId, this.state.studentsOrder);

    // const data: any = {};
    let needUpdate = false;
    const newUid = +memberId;
    const studentsOrder = [...this.state.studentsOrder];
    if (studentsOrder.length !== STAGE_NUM) {
      const len = STAGE_NUM - studentsOrder.length ? STAGE_NUM - studentsOrder.length : 0;
      if (len > 0) {
        Array(len).forEach(_ =>{
          studentsOrder.push(0)
        })
      }
    }
    if (studentsOrder.includes(newUid)) {
      return;
    } else {
      // 台上有空闲
      const emptyIndex = studentsOrder.indexOf(0);
      if (emptyIndex > -1) {
        //从台下找人
        studentsOrder.splice(emptyIndex, 1, +newUid)
        console.log('加入studentsOrder', newUid, studentsOrder);
        needUpdate = true;
      }
    }
    if (needUpdate) {
      // this.state = {
      //   ...this.state,
      //   studentsOrder
      // }
      // this.state.studentsOrder = studentsOrder;
      // await this.commit(this.state);
      // await this.rtmClient.addOrUpdateChannelAttributes({'studentsOrder': studentsOrder});
    }
    console.log('加入完登录学生后', this.state.studentsOrder);
  }

  async syncStageStudentByTeacherOrder2(newUsers: any[], oldUsers: any[]){

    console.log('new users', newUsers);
    console.log('current users', oldUsers);
    console.log('teacher keep studentsOrder', this.state.studentsOrder);
    const data: any = {};
    const newAccData: any = {};
    const newUids: number[] = []
    newUsers.forEach(([uid, userInfo]) =>{
      if (userInfo.role !== 'teacher') {
        newAccData[uid] = userInfo;
        newUids.push(+uid);
      }
    });

    const oldUids: number[] = []
    oldUsers.forEach( ([uid, userInfo]) => {
      if (userInfo.role !== 'teacher') {
        oldUids.push(+uid)
      }
    });
    const tSOrder = [...this.state.studentsOrder];
    const _to1 = [...tSOrder]
    _to1.sort((a, b) => a - b)
    const tSOrderStr1 = JSON.stringify(_to1)

    let shouldOut = oldUids.filter(x => !newUids.includes(x) && x > 0);
    let shouldIn = newUids.filter(x => !oldUids.includes(x) && x > 0);
    console.log('shouldOut', shouldOut);
    console.log('shouldIn', shouldIn);
    shouldOut.forEach(o => {
      this._userOut(+o);
      const oi = tSOrder.indexOf(o);
      if (oi > -1) {
        tSOrder.splice(oi, 1, 0);
        studentsOrderTs[oi] = 0;
      }
    })
    shouldIn.forEach(o => {
      this._userIn(+o);
      if(!tSOrder.includes(o)) {
        const ii = tSOrder.indexOf(0);
        if (ii > -1) {
          tSOrder.splice(ii, 1, o)
          studentsOrderTs[ii] = Date.now();
          const user = newAccData[o];
          if (user) {
            user.audio = 1;
            user.video = 1;
            data[o] = user;
          }
        }
      }
    })
    const _to2 = [...tSOrder]
    _to2.sort((a, b) => a - b)
    const tSOrderStr2 = JSON.stringify(_to2)
    console.log(tSOrderStr1, tSOrderStr2);
    if (tSOrderStr1 !== tSOrderStr2) {
      data['studentsOrder'] = [...tSOrder];
      console.log('new channel attr to every one');
      return data
    }
    // if (shouldIn.length || shouldOut.length) {
    //   data['studentsOrder'] = [...tSOrder];
    //   return data
    // }

  }

  async updateRoomAttrs ({teacher, accounts, room, studentsOrder, userStars}: {teacher: any, accounts: any, room: any, studentsOrder: number[], userStars: any}) {
    console.log("[agora-board], room:  ",teacher, accounts, room, studentsOrder);
    const currentRoomUsers = this.state.users.toArray();
    // const exitUserIds = [];
    const users: OrderedMap<string, AgoraUser> = accounts.reduce((acc: OrderedMap<string, AgoraUser>, it: any) => {
      roomStore.releaseWaitStreamHandle(it.uid);
      return acc.set(it.uid, {
        role: it.role,
        account: it.account,
        uid: it.uid,
        video: it.video,
        audio: it.audio,
        chat: it.chat,
        boardId: it.whiteboard_uid,
        sharedId: it.shared_uid,
        linkId: it.link_uid,
        lockBoard: it.lock_board,
        grantBoard: it.grant_board,
        lastStageTs: it.last_stage_ts,
        stars: it.stars || 0
      });
    }, OrderedMap<string, AgoraUser>());
    if (this.state.studentsOrder.length === 0) {
      this.state.studentsOrder = Array(STAGE_NUM).fill(0)
    }
    /*
    let users = OrderedMap<string, AgoraUser>();
    for (const it of accounts) {
      users = users.set(it.uid, {
        role: it.role,
        account: it.account,
        uid: it.uid,
        video: it.video,
        audio: it.audio,
        chat: it.chat,
        boardId: it.whiteboard_uid,
        sharedId: it.shared_uid,
        linkId: it.link_uid,
        lockBoard: it.lock_board,
        grantBoard: it.grant_board,
      })
    }
    */
    console.log('users ', users.toArray());


    const me = this.state.me;

    if (users.get(me.uid)) {
      Object.assign(me, users.get(me.uid));
    }
    // 每个学生都要维护一下状态，因为可能涉及到自己的上下台

    // if (newInfo && newInfo['studentsOrder'] && me.role == 'student') {
    //   if (newInfo['studentsOrder'].includes(+this.state.me.uid)) {
    //     this.meUp();
    //   } else {
    //     this.meDown()
    //   }
    // }

    let newStudentsOrder = studentsOrder;
    const uos = JSON.stringify(this.state.studentsOrder);
    let cos = JSON.stringify(studentsOrder);
    console.log('频道StudentsOrder', newStudentsOrder);
    console.log('用户保存StudentsOrder', this.state.studentsOrder);
    // let updateOrders = false;
    if (me.role === 'teacher') {
      /*
      // remove exist user  from studentsOrder
      for (const uid of [...studentsOrder]) {
        if (uid <= 0) {
          continue
        }
        const has = users.get(`${uid}`)
        if (!has) {
          studentsOrder.splice(studentsOrder.indexOf(+uid), 1)
        }
      }*/


      newStudentsOrder = [...this.state.studentsOrder];//studentsOrder;
      Object.assign(me, {
        linkId: room.link_uid,
        boardId: room.whiteboard_uid,
        lockBoard: room.lock_board,
      })

      if (this.syncStudentsOrderTimer) {
        // @ts-ignore
        clearTimeout(this.syncStudentsOrderTimer);
        this.syncStudentsOrderTimer = null;
      }
      //教师维护学生顺序,仅保留STAGE_NUM个，并更新到频道属性
      // const newInfo: any = await this.syncStageStudentByTeacherOrder(JSON.parse(JSON.stringify(accounts)), [...newStudentsOrder/*this.state.studentsOrder*/]);


      const newInfo: any = await this.syncStageStudentByTeacherOrder2(users.toArray(), currentRoomUsers);
      console.log('newInfo', newInfo)

      if (newInfo) {
        // updateOrders = true;
        newStudentsOrder = newInfo['studentsOrder'];
        cos = JSON.stringify(newInfo['studentsOrder']);
        await this.rtmClient.addOrUpdateChannelAttributes(newInfo);
        // @ts-ignore
        // this.syncStudentsOrderTimer = setTimeout(async () => {
        // }, 1000)
      } else {
        // newStudentsOrder = [];
      }

    }

    const newClassState: ClassState = {} as ClassState;
    Object.assign(newClassState, {
      teacherId: get(teacher, 'uid', 0),
      linkId: room.link_uid,
      boardId: room.whiteboard_uid,
      courseState: room.class_state,
      muteChat: room.mute_chat,
      // muteAudio: room.mute_audio,
      lockBoard: room.lock_board,
      currentTool: room.current_tool
    })

    // console.log("... me", this.state.me);
    // console.log("... this.state.me", me);

    this.state = {
      ...this.state,
      users,
      me: {
        ...this.state.me,
        ...me,
      },
      course: {
        ...this.state.course,
        ...newClassState
      },
      // studentsOrder: newStudentsOrder
      userStars
    }
    console.log('update orders?', cos, uos)
    if (cos !== uos) {
      this.state = {
        ...this.state,
        studentsOrder: newStudentsOrder
      }
    }
    // if (me.role !== 'teacher') {
    //   this.state = {
    //     ...this.state,
    //     userStars
    //   }
    // }
    await this.commit(this.state);
  }

  updateSessionInfo (info: any) {
    this.state = {
      ...this.state,
      course: {
        ...this.state.course,
        rid: info.rid,
        roomName: info.roomName,
        roomType: info.roomType
      },
      me: {
        ...this.state.me,
        account: info.account,
        uid: info.uid,
        role: info.role,
        video: info.video,
        audio: info.audio,
        chat: info.chat,
        linkId: info.linkId,
        sharedId: info.sharedId,
        boardId: info.boardId,
      }
    }
    this.commit(this.state);
  }
  async removeRtcListener() {

    const iframes = document.querySelectorAll("iframe");
    if (iframes && iframes.length) {
      iframes.forEach(ifm => {
        // @ts-ignore
        ifm.contentWindow.location.href='about:blank'
        // @ts-ignore
        ifm.remove()
      })
    }
    /*
    const events = [
      'onTokenPrivilegeWillExpire',
      'onTokenPrivilegeDidExpire',
      'error',
      'stream-published',
      'stream-subscribed',
      'stream-added',
      'stream-removed',
      'peer-online',
      'peer-leave',
      'stream-fallback'
    ]
    */
    const webClient = roomStore.rtcClient as AgoraWebClient;
    // for (let eventName of events) {
    //   webClient.rtc.off(eventName, () => {});
    // }
    webClient.rtc.offAll();
    webClient.rtc.removeAllListeners();
    console.log("[agora-web] rtmClient logout");
    try{
      await roomStore.rtmClient.logout();
    } catch (e) {

    }

    console.log("[agora-web] remove event listener");
    webClient.exit().then(() => {
      console.log("[agora-web] do remove event listener");
    }).catch(console.warn)
      .finally(() => {
        try{
          this.removeLocalStream();
        } catch (e) {

        }
      });

  }
  async exitAll() {
    console.log('exitAll');

    try {
      if (this.state.me.role === 'teacher') {
        await this.rtmClient.clearChannelAttributes()
      } else {
        await this.rtmClient.clearChannelAttributesByKeys([`${this.state.me.uid}`]);
      }
      try{
        await this.rtmClient.logout();
      } catch(e) {}

      // const webClient = roomStore.rtcClient as AgoraWebClient;
      // setTimeout(()=>{
        // alert('clear2')
        GlobalStorage.clear('agora_room');
      // })
      await this.removeRtcListener();

      // await this.rtcClient.exit();

    } catch(err) {

      console.warn('exitAll', err);
    } finally {

      this.state = {} as RoomState;
      this.state = {
        ...this.defaultState,
        studentsOrder: [],
        userUsage: {},
        handsUpStudents: {}
      }
      this.commit(this.state);
    }
  }

  setScreenShare(shared: boolean) {
    this.state = {
      ...this.state,
      rtc: {
        ...this.state.rtc,
        shared,
      }
    }
    this.commit(this.state);
  }
}

export const roomStore = new RoomStore();
class WindowMonitor  {
  roomStore: RoomStore ;
  constructor(roomStore: RoomStore) {
    this.roomStore = roomStore
  }
  b4Leave(e: any){
    if (this.roomStore.state.me.role !== 'teacher') {
      return
    }
    return "确定退出吗?";

    // e.returnValue = '确定退出吗?';
    // console.log(999999888888)
    // if(window.confirm("确定退出吗")) {
    //   var blob = new Blob(['room_id=123'], {
    //     type: 'application/x-www-form-urlencoded'
    //   });
    //   navigator.sendBeacon("http://127.0.0.1:8686/user/register", blob);
    // }
  }
  leave(){
    if (this.roomStore.state.me.role !== 'teacher') {
      return
    }
    const  userUsage = this.roomStore.getUserUsage();
    console.log('userUsage', userUsage);
    const data = {
      class_id: this.roomStore.state.course.rid,
      uid: 0,
      time: 0,
      students: []
    }

    const now = Date.now();
    const students: any = [];
    Object.values(userUsage).forEach( u => {
      let _e = now;
      if (u.out !== 0 ) {
        _e = u.out;
      }
      const time = u.all + (u.in > 0 ? (_e - u.in) : 0);
      if (u.role === "student") {
        students.push({
          uid: u.uid,
          time
        })
      } else if (u.role === 'teacher') {
        data.time = time;
        data.uid = +this.roomStore.state.course.teacherId;
      }
    })
    data.students = students;
    console.log(data);

    const host = getApiServerHost(true);
    var blob = new Blob([JSON.stringify(data)], {
      type: 'text/plain'
    });
    navigator.sendBeacon(`${host}/api/classroom/finish`, blob);
    /*try{
      console.log(11111)
      roomStore.exitAll()
      console.log(222222)
    } catch (e) {
     alert(e.message)
    }*/
    console.log(8888888)
  }
  hasUnloadEvent(){
    return !!window.onbeforeunload;
  }
  addUnloadEvent(){
    if (this.roomStore.state.me.role !== 'teacher') {
      return
    }
    if (this.hasUnloadEvent()) {
      return
    }
    window.onbeforeunload = this.b4Leave
    window.onunload = this.leave
  }
  removeUnloadEvent() {
    if (this.roomStore.state.me.role !== 'teacher') {
      return
    }
    window.onbeforeunload = null
    window.onunload = null
  }
}
export const windowMonitor = new WindowMonitor(roomStore);
//@ts-ignore
window.roomStore = roomStore;
