text: 'use strict';
  
  const Assert = require('assert');
  const Events = require('events');
  const Packet = require('./packet');
  const WebSocket = require('./backend');
  const Socket = require('./socket');
  
  class Server extends Events {
    constructor(options = {}) {
      super();
  
      Assert(!options.protocols || Array.isArray(options.protocols));
  
      this.protocols = options.protocols || undefined;
      this.sockets = new Set();
      this.channels = new Map();
      this.mounts = [];
      this.mounted = false;
    }
  
    handleSocket(socket) {
      this.add(socket);
  
      socket.on('close', () => {
        this.remove(socket);
      });
  
      this.emit('socket', socket);
  
      for (const server of this.mounts)
        server.emit('socket', socket);
    }
  
    mount(server) {
      Assert(!server.mounted);
      server.mounted = true;
      server.sockets = this.sockets;
      server.channels = this.channels;
      this.mounts.push(server);
    }
  
    async open() {
      ;
    }
  
    async close() {
      if (this.mounted)
        return;
  
      for (const socket of this.sockets)
        socket.destroy();
    }
  
    attach(server) {
      server.on('upgrade', (req, socket, body) => {
        if (!socket.remoteAddress) {
          socket.destroy();
          return;
        }
  
        if (!WebSocket.isWebSocket(req)) {
          socket.destroy();
          return;
        }
  
        const ws = new WebSocket(req, socket, body, this.protocols);
        const sock = Socket.accept(this, req, socket, ws);
  
        this.handleSocket(sock);
      });
  
      return this;
    }
  
    add(socket) {
      this.sockets.add(socket);
    }
  
    remove(socket) {
      for (const name of socket.channels)
        this.leave(socket, name);
  
      Assert(this.sockets.delete(socket));
    }
  
    join(socket, name) {
      if (socket.channels.has(name))
        return false;
  
      if (!this.channels.has(name))
        this.channels.set(name, new Set());
  
      const sockets = this.channels.get(name);
  
      sockets.add(socket);
      socket.channels.add(name);
  
      return true;
    }
  
    leave(socket, name) {
      if (!socket.channels.has(name))
        return false;
  
      const sockets = this.channels.get(name);
  
      Assert(sockets);
      Assert(sockets.delete(socket));
  
      if (sockets.size === 0)
        this.channels.delete(name);
  
      socket.channels.delete(name);
  
      return true;
    }
  
    channel(name) {
      const sockets = this.channels.get(name);
  
      if (!sockets)
        return null;
  
      Assert(sockets.size > 0);
  
      return sockets;
    }
  
    event(args) {
      Assert(args.length > 0, 'Event must be present.');
      Assert(typeof args[0] === 'string', 'Event must be a string.');
      const packet = new Packet();
      packet.type = Packet.TYPES.EVENT;
      packet.setData(args);
      return packet;
    }
  
    to(name, ...args) {
      const sockets = this.channels.get(name);
  
      if (!sockets)
        return;
  
      Assert(sockets.size > 0);
  
      const packet = this.event(args);
  
      for (const socket of sockets)
        socket.sendPacket(packet);
    }
  
    all(...args) {
      const packet = this.event(args);
  
      for (const socket of this.sockets)
        socket.sendPacket(packet);
    }
  
    static attach(parent, options) {
      const server = new this(options);
      return server.attach(parent);
    }
  
    static createServer(options) {
      return new this(options);
    }
  }
  
  module.exports = Server;