// dtpweb-socket.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0

'use strict';

const DTP_COMPONENT = { name: 'Socket', slug: 'socket' };

window.dtp = window.dtp || { };

import DtpWebLog from './dtp-log.js';

export default class DtpWebSocket {

  constructor ( ) {
    this.isConnected = false;
    this.isAuthenticated = false;
    this.joinedChannels = { };
    this.log = new DtpWebLog(DTP_COMPONENT);
  }

  async connect (options) {
    options = Object.assign({
      withRetry: true,
      withError: false,
    }, options);
    try {
      const response = await fetch('/auth/socket-token');
      if (!response.ok) {
        this.retryConnect();
        throw new Error('socket failed to connect');
      }
  
      const json = await response.json();
      if (!json.success) {
        this.retryConnect();
        throw new Error(`failed to connect: ${json.message}`);
      }

      this.log.debug('connect', 'WebSocket connecting to Digital Telepresence Platform', json);
      this.socket = io('/', {
        transports: ['websocket'],
        reconnection: false,
        auth: {
          token: json.token,
        },
      });
  
      this.socket.on('connect', this.onSocketConnect.bind(this));
      this.socket.on('authenticated', this.onSocketAuthenticated.bind(this));
      this.socket.on('join-result', this.onJoinResult.bind(this));
      this.socket.on('disconnect', this.onSocketDisconnect.bind(this));
    } catch (error) {
      this.log.error('connect', 'failed to connect', { error });
      if (options.withRetry) {
        this.retryConnect();
      }
      if (options.withError) {
        throw error;
      }
    }
  }

  async onSocketConnect ( ) {
    this.log.info('onSocketConnect', 'WebSocket connected');
    this.isConnected = true;
    if (this.disconnectDialog) {
      this.disconnectDialog.hide();
      delete this.disconnectDialog;
    }
  }

  async onSocketDisconnect (reason) {
    this.isConnected = false;
    this.socket.close();
    delete this.socket;

    if (!this.isAuthenticated) {
      UIkit.modal.alert(`Failed to authenticate WebSocket session. Please log out, log back in, and try again.`);
      return;
    }

    const REASONS = {
      'io server disconnect': 'The server closed the connection',
      'io client disconnect': 'The client closed the connection',
      'ping timeout': 'The server is unreachable',
      'transport close': 'Connection was lost or network changed',
      'transport error': 'Server communication error, please try again.',
    };

    this.log.warn('onSocketDisconnect', 'WebSocket disconnected', { reason });
    const modal = UIkit.modal.alert(`Disconnected: ${REASONS[reason]}`);
    this.disconnectDialog = modal.dialog;

    UIkit.util.on(modal.dialog.$el, 'hidden', ( ) => {
      this.log.info('onSocketDisconnect', 'disconnect dialog closed');
      delete this.disconnectDialog;
    });

    this.retryConnect();
  }

  retryConnect ( ) {
    if (this.retryTimeout) {
      return; // a retry is already in progress
    }
    this.retryTimeout = setTimeout(async ( ) => {
      delete this.retryTimeout;
      await this.connect();
    }, 2000);
  }

  async onSocketAuthenticated (message) {
    this.log.info('onSocketAuthenticated', message.message, { user: message.user });

    this.isAuthenticated = true;
    this.user = message.user;

    this.joinChannel(message.user._id, 'user');
    document.dispatchEvent(new Event('socketConnected'));
  }

  async joinChannel (channelId, channelType) {
    this.log.info('joinChannel', 'joining channel', { channelId, channelType });
    this.socket.emit('join', { channelId, channelType });
  }

  async onJoinResult (message) {
    this.log.info('onJoinResult', 'channel joined', { message });
    document.dispatchEvent(new Event('socketChannelJoined', { channelId: message.channelId }));
  }

  async leaveChannel (channelId) {
    this.log.info('leaveChannel', 'leaving channel', { channelId });
    this.socket.emit('leave', { channelId });
  }

  async emit (messageName, payload) {
    this.log.info('emit', 'sending message', { messageName, payload });
    this.socket.emit(messageName, payload);
  }
}