import * as Logger from "../utils/logger.js";

export class Signaling extends EventTarget {

  constructor(interval = 1000, baseUrl = null) {
    super();
    this.running = false;
    this.interval = interval;
    this.baseUrl = baseUrl;
    this.sleep = msec => new Promise(resolve => setTimeout(resolve, msec));
  }

  headers() {
    if (this.sessionId !== undefined) {
      return { 'Content-Type': 'application/json', 'Session-Id': this.sessionId };
    }
    else {
      return { 'Content-Type': 'application/json' };
    }
  }

  url(method, parameter = '') {
    let ret = (this.baseUrl || location.origin) + '/signaling';
    if (method)
      ret += '/' + method;
    if (parameter)
      ret += '?' + parameter;
    return ret;
  }

  async start() {
    if (this.running) {
      return;
    }

    this.running = true;
    while (!this.sessionId) {
      const createResponse = await fetch(this.url(''), { method: 'PUT', headers: this.headers() });
      const session = await createResponse.json();
      this.sessionId = session.sessionId;

      if (!this.sessionId) {
        await this.sleep(this.interval);
      }
    }

    this.loopGetAll();
  }

  async loopGetAll() {
    let lastTimeRequest = Date.now() - 30000;
    while (this.running) {
      const res = await this.getAll(lastTimeRequest);
      const data = await res.json();
      lastTimeRequest = data.datetime ? data.datetime : Date.now();

      const messages = data.messages;

      for (const msg of messages) {
        switch (msg.type) {
          case "connect":
            break;
          case "disconnect":
            this.dispatchEvent(new CustomEvent('disconnect', { detail: msg }));
            break;
          case "offer":
            this.dispatchEvent(new CustomEvent('offer', { detail: msg }));
            break;
          case "answer":
            this.dispatchEvent(new CustomEvent('answer', { detail: msg }));
            break;
          case "candidate":
            this.dispatchEvent(new CustomEvent('candidate', { detail: msg }));
            break;
          case "on-message":
            {
              let parsed = msg.data;
              if (typeof msg.data === 'string') {
                try { parsed = JSON.parse(msg.data); } catch(e) {
                  Logger.error(`Signaling: on-message, error: ${e}`);
                }
              }
              this.dispatchEvent(new CustomEvent('on-message', { detail: parsed }));
            }
            break;
          default:
            break;
        }
      }
      await this.sleep(this.interval);
    }
  }

  async stop() {
    this.running = false;
    await fetch(this.url(''), { method: 'DELETE', headers: this.headers() });
    this.sessionId = null;
  }

  async createConnection(connectionId) {
    const data = { 'connectionId': connectionId };
    const res = await fetch(this.url('connection'), { method: 'PUT', headers: this.headers(), body: JSON.stringify(data) });
    const json = await res.json();
    Logger.log(`Signaling: HTTP create connection, connectionId: ${json.connectionId}, polite:${json.polite}`);

    this.dispatchEvent(new CustomEvent('connect', { detail: json }));
    return json;
  }

  async deleteConnection(connectionId) {
    const data = { 'connectionId': connectionId };
    const res = await fetch(this.url('connection'), { method: 'DELETE', headers: this.headers(), body: JSON.stringify(data) });
    const json = await res.json();
    this.dispatchEvent(new CustomEvent('disconnect', { detail: json }));
    return json;
  }

  async sendOffer(connectionId, sdp) {
    const data = { 'sdp': sdp, 'connectionId': connectionId };
    Logger.log('sendOffer:' + data);
    await fetch(this.url('offer'), { method: 'POST', headers: this.headers(), body: JSON.stringify(data) });
  }

  async sendAnswer(connectionId, sdp) {
    const data = { 'sdp': sdp, 'connectionId': connectionId };
    Logger.log('sendAnswer:' + data);
    await fetch(this.url('answer'), { method: 'POST', headers: this.headers(), body: JSON.stringify(data) });
  }

  async sendCandidate(connectionId, candidate, sdpMid, sdpMLineIndex) {
    const data = {
      'candidate': candidate,
      'sdpMLineIndex': sdpMLineIndex,
      'sdpMid': sdpMid,
      'connectionId': connectionId
    };
    Logger.log('sendCandidate:' + data);
    await fetch(this.url('candidate'), { method: 'POST', headers: this.headers(), body: JSON.stringify(data) });
  }
  // 在 Signaling 类中添加
  async sendMessage(connectionId, message) {
    const data = {
      'message': message,
      'connectionId': connectionId
    };
    await fetch(this.url('on-message'), { method: 'POST', headers: this.headers(), body: JSON.stringify(data) });
  }
  async getAll(fromTime = 0) {
    return await fetch(this.url(``, `fromtime=${fromTime}`), { method: 'GET', headers: this.headers() });
  }
}

export class WebSocketSignaling extends EventTarget {

  constructor(interval = 1000, websocketUrl = null) {
    super();
    this.interval = interval;
    this.sleep = msec => new Promise(resolve => setTimeout(resolve, msec));

    if (!websocketUrl) {
      if (location.protocol === "https:") {
        websocketUrl = "wss://" + location.host;
      } else {
        websocketUrl = "ws://" + location.host;
      }
    }

    this.websocket = new WebSocket(websocketUrl);
    this.connectionId = null;

    this.websocket.onopen = () => {
      this.isWsOpen = true;
    };

    this.websocket.onclose = () => {
      this.isWsOpen = false;
    };

    this.websocket.onmessage = (event) => {
      const msg = JSON.parse(event.data);
      if (!msg || !this) {
        return;
      }

      Logger.log(msg);

      switch (msg.type) {
        case "connect":
          this.dispatchEvent(new CustomEvent('connect', { detail: msg }));
          break;
        case "disconnect":
          this.dispatchEvent(new CustomEvent('disconnect', { detail: msg }));
          break;
        case "offer":
          this.dispatchEvent(new CustomEvent('offer', { detail: { connectionId: msg.from, sdp: msg.data.sdp, polite: msg.data.polite, participantId: msg.participantId } }));
          break;
        case "answer":
          this.dispatchEvent(new CustomEvent('answer', { detail: { connectionId: msg.from, sdp: msg.data.sdp, participantId: msg.participantId } }));
          break;
        case "candidate":
          this.dispatchEvent(new CustomEvent('candidate', { detail: { connectionId: msg.from, candidate: msg.data.candidate, sdpMLineIndex: msg.data.sdpMLineIndex, sdpMid: msg.data.sdpMid, participantId: msg.participantId } }));
          break;
        case "on-message":
          {
            let parsed = msg.data;
            if (typeof msg.data === 'string') {
              try { parsed = JSON.parse(msg.data); } catch(e) {
                Logger.error(`Signaling: on-message, error: ${e}`);
              }
            }
            if (msg.participantId) {
              parsed.participantId = msg.participantId;
            }
            this.dispatchEvent(new CustomEvent('on-message', { detail: parsed }));
          }
          break;
        case "participant-left":
          this.dispatchEvent(new CustomEvent('participant-left', { detail: msg }));
          break;
        case "participant-joined":
          this.dispatchEvent(new CustomEvent('participant-joined', { detail: msg }));
          break;
        case "broadcast":
          this.dispatchEvent(new CustomEvent('on-message', { detail: msg.message }));
          break;
        case "invite-call":
          this.dispatchEvent(new CustomEvent('invite-call', { detail: msg.data }));
          break;
        case "invite-accepted":
          this.dispatchEvent(new CustomEvent('invite-accepted', { detail: msg.data }));
          break;
        case "invite-rejected":
          this.dispatchEvent(new CustomEvent('invite-rejected', { detail: msg.data }));
          break;
        case "invite-failed":
          this.dispatchEvent(new CustomEvent('invite-failed', { detail: msg.data }));
          break;
        default:
          break;
      }
    };
  }

  async start() {
    while (!this.isWsOpen) {
      await this.sleep(100);
    }
  }

  async stop() {
    this.websocket.close();
    while (this.isWsOpen) {
      await this.sleep(100);
    }
  }

  createConnection(connectionId) {
    const sendJson = JSON.stringify({ type: "connect", connectionId: connectionId });
    Logger.log(sendJson);
    this.websocket.send(sendJson);
  }

  deleteConnection(connectionId) {
    const sendJson = JSON.stringify({ type: "disconnect", connectionId: connectionId });
    Logger.log(sendJson);
    this.websocket.send(sendJson);
  }

  sendOffer(connectionId, sdp, participantId) {
    const data = { 'sdp': sdp, 'connectionId': connectionId };
    const sendJson = JSON.stringify({ type: "offer", from: connectionId, data: data, participantId: participantId || '' });
    Logger.log(sendJson);
    this.websocket.send(sendJson);
  }

  sendAnswer(connectionId, sdp, participantId) {
    const data = { 'sdp': sdp, 'connectionId': connectionId };
    const sendJson = JSON.stringify({ type: "answer", from: connectionId, data: data, participantId: participantId || '' });
    Logger.log(sendJson);
    this.websocket.send(sendJson);
  }

  sendCandidate(connectionId, candidate, sdpMLineIndex, sdpMid, participantId) {
    const data = {
      'candidate': candidate,
      'sdpMLineIndex': sdpMLineIndex,
      'sdpMid': sdpMid,
      'connectionId': connectionId
    };
    const sendJson = JSON.stringify({ type: "candidate", from: connectionId, data: data, participantId: participantId || '' });
    Logger.log(sendJson);
    this.websocket.send(sendJson);
  }
  // 在 WebSocketSignaling 类中添加
  sendMessage(connectionId, message) {
    const data = {
      'message': message,
      'senderId': message.senderId,
      'connectionId': connectionId
    };
    const sendJson = JSON.stringify({ type: "on-message", data: data });
    Logger.log(sendJson);
    this.websocket.send(sendJson);
  }

  sendUserInfo(payload) {
    const sendJson = JSON.stringify({ type: 'host-userInfo', data: payload });
    Logger.log(sendJson);
    this.websocket.send(sendJson);
  }

  sendInviteCall(payload) {
    const sendJson = JSON.stringify({ type: 'invite-call', data: payload });
    Logger.log(sendJson);
    this.websocket.send(sendJson);
  }

  sendInviteAccepted(payload) {
    const sendJson = JSON.stringify({ type: 'invite-accepted', data: payload });
    Logger.log(sendJson);
    this.websocket.send(sendJson);
  }

  sendInviteRejected(payload) {
    const sendJson = JSON.stringify({ type: 'invite-rejected', data: payload });
    Logger.log(sendJson);
    this.websocket.send(sendJson);
  }
}
