import Backoff from 'backo';

export default class Socket {
  constructor(host) {
    const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
    this.url = `${protocol}://${host}/ws${window.location.pathname}`;

    this.connectTimeout = 20000;
    this.pingTimeout = 30000;
    this.backoff = new Backoff({
      min: 1000,
      max: 5000,
      jitter: 0.25
    });
    this.handlers = [];
    this.connected = false;
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.timeoutConnect = setTimeout(() => {
      this.ws.close();
      this.retry();
    }, this.connectTimeout);

    this.ws.onopen = () => {
      this.connected = true;
      this.emit('_connected', true);
      clearTimeout(this.timeoutConnect);
      this.backoff.reset();
      this.setTimeoutPing();
    };

    this.ws.onclose = () => {
      if (this.connected) {
        this.connected = false;
        this.emit('_connected', false);
      }
      clearTimeout(this.timeoutConnect);
      clearTimeout(this.timeoutPing);
      if (!this.closing) {
        this.retry();
      }
      this.closing = false;
    };

    this.ws.onerror = () => {
      clearTimeout(this.timeoutConnect);
      clearTimeout(this.timeoutPing);
      this.closing = true;
      this.ws.close();
      this.retry();
    };

    this.ws.onmessage = e => {
      this.setTimeoutPing();

      const msg = JSON.parse(e.data);

      if (msg.type === 'ping') {
        this.send('pong');
        return;
      }

      this.emit(msg.type, msg.data);
    };
  }

  retry() {
    setTimeout(() => this.connect(), this.backoff.duration());
  }

  send(type, data) {
    this.ws.send(JSON.stringify({ type, data }));
  }

  setTimeoutPing() {
    clearTimeout(this.timeoutPing);
    this.timeoutPing = setTimeout(() => {
      this.closing = true;
      this.ws.close();
      this.connect();
    }, this.pingTimeout);
  }

  onMessage(handler) {
    this.handlers.push(handler);
  }

  emit(type, data) {
    for (let i = 0; i < this.handlers.length; i++) {
      this.handlers[i](type, data);
    }
  }
}
