import _ from "underscore";
import { EventEmitter } from "events";
import util from "util";
import uuid from "uuid";

class Room {
  constructor(io = {}, id = {}) {
    this._id = id;
    this.numUsers = 0;
    this.users = [];
    // Should probably abstract this to a new class
    this.rateLimitQueue = [];

    EventEmitter.call(this);

    const thisIO = io.of(this._id);

    thisIO.on("connection", socket => {
      let addedUser = false;

      // when the client emits 'new message', this listens and executes
      socket.on("new message", data => {
        const { username } = socket;
        const isRateLimited = this.isRateLimited(username);

        if (isRateLimited) {
          return thisIO.emit("rated", { username, id: socket.user.id });
        }

        // we tell the client to execute 'new message'
        socket.broadcast.emit("new message", {
          username,
          id: socket.user.id,
          message: data.message,
          messageType: data.messageType,
          data: data.data,
          vector: data.vector,
          secretKeys: data.secretKeys,
          signature: data.signature
        });
      });

      socket.on("add user", data => {
        if (addedUser) {
          return;
        }

        data.id = uuid.v4();

        this.users.push(data);

        const username = this.sanitizeUsername(data.username);

        // we store the username in the socket session for this client
        socket.username = username;
        socket.user = data;
        ++this.numUsers;
        addedUser = true;

        // Broadcast to ALL sockets, including this one
        thisIO.emit("user joined", {
          username: socket.username,
          numUsers: this.numUsers,
          users: this.users
        });
      });

      // when the client emits 'typing', we broadcast it to others
      socket.on("typing", () => {
        socket.broadcast.emit("typing", { username: socket.username });
      });

      // when the client emits 'stop typing', we broadcast it to others
      socket.on("stop typing", () => {
        socket.broadcast.emit("stop typing", { username: socket.username });
      });

      // when the user disconnects.. perform this
      socket.on("disconnect", () => {
        if (addedUser) {
          --this.numUsers;
          this.users = _.without(this.users, socket.user);

          // echo globally that this client has left
          socket.broadcast.emit("user left", {
            username: socket.username,
            numUsers: this.numUsers,
            users: this.users,
            id: socket.user.id
          });

          // remove room from rooms array
          if (this.numUsers === 0) {
            this.emit("empty");
          }
        }
      });

      // Update user
      socket.on("update user", data => {
        const newUsername = this.sanitizeUsername(data.newUsername);

        if (newUsername.length > 16) {
          return false;
        }

        const user = _.find(this.users, users => {
          return users === socket.user;
        });

        if (user) {
          socket.username = user.username = newUsername;
          socket.user = user;

          thisIO.emit("user update", {
            username: socket.username,
            id: socket.user.id
          });
        }
      });
    });
  }

  isRateLimited(username) {
    if (this.rateLimitQueue.indexOf(username) > -1) {
      return true;
    }

    this.triggerRateLimitOn(username);
    return false;
  }

  triggerRateLimitOn(username) {
    this.addToRateQueue(username);
    setTimeout(
      () => {
        this.removeFromRateQueue(username);
      },
      120
    );
  }

  addToRateQueue(username) {
    this.rateLimitQueue.push(username);
    return this;
  }

  removeFromRateQueue(username) {
    this.rateLimitQueue = _.without(this.rateLimitQueue, username);
    return this;
  }

  sanitizeUsername(str) {
    return str.replace(/[^A-Za-z0-9]/g, "-");
  }

  roomId() {
    return this.id;
  }
}

util.inherits(Room, EventEmitter);

export default Room;

