/**
   * Listens for a topic being removed from the space for either a MainMenuController or a
   * ChatroomController.
   */
  @Override
  public void notify(RemoteEvent remoteEvent) throws UnknownEventException, RemoteException {
    try {
      // Get the event that triggered the notification
      AvailabilityEvent availEvent = (AvailabilityEvent) remoteEvent;
      JMSTopicDeleted topicDeleted = (JMSTopicDeleted) availEvent.getEntry();

      if (isChatroomController) {
        // If the listener is for a ChatroomController, inform the given
        // chatroom that the chatroom's topic has been deleted
        chatroomController.handleTopicDeleted();
      } else {
        // If the listener is for a MainMenuController, remove the given
        // topic from the MainMenuController's topic list.
        DefaultTableModel topicTableModel = mainMenuController.getTopicTableModel();

        // Loop through all of the topics in the MainMenuController's
        // topic list and remove the one that was deleted
        for (int i = 0; i < topicTableModel.getRowCount(); i++) {
          UUID topicIdInTable = (UUID) topicTableModel.getValueAt(i, 3);
          JMSTopic topicRemoved = topicDeleted.getTopic();
          UUID topicDeletedId = topicRemoved.getId();

          if (topicIdInTable.equals(topicDeletedId)) {
            topicTableModel.removeRow(i);

            break;
          }
        }
      }
    } catch (Exception e) {
      System.err.println("Failed to remove topic from list or send notifications to users.");
      e.printStackTrace();
    }
  }
  /**
   * Checks if a topic has any null unique identifiers (base name, name, and UUID)
   *
   * @param topic
   * @return <code>true</code> if any unique identifier is null, otherwise <code>false</code>
   */
  private boolean isValidTopic(JMSTopic topic) {
    if (StringUtils.isNotBlank(topic.getBaseName())
        && StringUtils.isNotBlank(topic.getName())
        && topic.getId() != null) {
      return true;
    }

    return false;
  }
  /**
   * Looks for a topic in the space with the same base name.
   *
   * @param baseName The base name to search for.
   * @return If a matching topic exists, that topic is returned, otherwise <code>null</code> is
   *     returned.
   */
  public JMSTopic getTopicByBaseName(String baseName) {
    JMSTopic template = new JMSTopic();
    template.setBaseName(baseName);

    JMSTopic topicFound = null;

    try {
      topicFound = (JMSTopic) space.readIfExists(template, null, 1000);
    } catch (Exception e) {
      System.err.println("Failed to get topic by base name. Base name queried: '" + baseName + "'");
      e.printStackTrace();
    }

    return topicFound;
  }
  /**
   * Deletes a topic, including all of its messages and TopicUsers
   *
   * @param topic The topic to delete
   * @param userRequestingDeletion The user who requested the topic be deleted
   * @throws AccessDeniedException
   */
  public void deleteTopic(JMSTopic topic, JMSUser userRequestingDeletion)
      throws AccessDeniedException {
    if (userRequestingDeletion.equals(topic.getOwner())) {
      // If the topic does not contain null fields
      if (isValidTopic(topic)) {
        try {
          Transaction transaction = TransactionHelper.getTransaction(10000l);

          space.takeIfExists(topic, transaction, 3000l);

          deleteAllTopicUsers(topic, transaction);
          MessageService.getMessageService().deleteAllTopicMessages(topic, transaction);

          // Writes a JMSTopicDeleted object so listeners know the
          // topic
          // has been removed
          space.write(new JMSTopicDeleted(topic), transaction, 1000l * 60l);

          transaction.commit();
        } catch (Exception e) {
          e.printStackTrace();
        }
      } else {
        System.err.println(
            "Attempted to delete topic with on or more null fields.  "
                + "Due to how JavaSpaces work, this will delete one at random and is not allowed.");
      }
    } else {
      throw new AccessDeniedException("Only the topic's owner can delete the topic");
    }
  }
  /**
   * Looks for a topic in the space with the same UUID.
   *
   * @param baseName The UUID to search for.
   * @return If a matching topic exists, that topic is returned, otherwise <code>null</code> is
   *     returned.
   */
  public JMSTopic getTopicById(UUID id) {
    JMSTopic template = new JMSTopic();
    template.setId(id);

    JMSTopic topic = null;

    try {
      topic = (JMSTopic) space.readIfExists(template, null, 1000);
    } catch (RemoteException
        | UnusableEntryException
        | TransactionException
        | InterruptedException e) {
      System.err.println("Failed to get topic by ID.  Topic ID queried: '" + id.toString() + "'");
      e.printStackTrace();
    }

    return topic;
  }
  /**
   * Whether or not a topic already exists in the space with the same base name or UUID.
   *
   * @param topic The topic to check if exists
   * @param transaction The transaction in which the check will take place
   * @return <code>true</code> if a match by either base name or UUID, otherwise <code>false</code>
   */
  private boolean topicExistsInSpace(JMSTopic topic, Transaction transaction) {
    try {
      JMSTopic template = new JMSTopic();
      template.setBaseName(topic.getBaseName());
      JMSTopic topicBaseNameMatch = (JMSTopic) space.readIfExists(template, transaction, 2000);

      template = new JMSTopic();
      template.setId(topic.getId());
      JMSTopic topicIdMatch = (JMSTopic) space.readIfExists(template, transaction, 2000);

      if (topicBaseNameMatch != null || topicIdMatch != null) {
        return true;
      }
    } catch (Exception e) {
      e.printStackTrace();
    }

    return false;
  }
  /**
   * Removes all users from a given topic.
   *
   * @param topic A topic to remove all of the users from.
   * @param transaction The transaction in which to run the delete
   */
  private void deleteAllTopicUsers(JMSTopic topic, Transaction transaction) {
    JMSTopicUser template = new JMSTopicUser(topic);

    try {
      while (space.readIfExists(template, transaction, 1000) != null) {
        space.takeIfExists(template, transaction, 1000);
      }
    } catch (RemoteException
        | UnusableEntryException
        | TransactionException
        | InterruptedException e) {
      System.err.println(
          "Failed to delete all users from Topic entitled: '"
              + topic.getName()
              + "' with ID: '"
              + topic.getId().toString()
              + "'");
      e.printStackTrace();
    }
  }
  /**
   * Removes a given user from a given topic
   *
   * @param topic The topic to remove the user from
   * @param user The user to remove from the topic
   */
  public void removeTopicUser(JMSTopic topic, JMSUser user) {
    try {
      Transaction transaction = TransactionHelper.getTransaction();

      boolean removed = false;
      JMSTopicUser template = new JMSTopicUser(topic, user);

      // If the space is in a bad state and has duplicate users in a
      // topic, this while loop ensures they are all removed
      while (space.takeIfExists(template, transaction, 1000) != null) {
        removed = true;
      }

      // Put a JMSTopicUserRemoved object in the space so listeners can
      // pick them up and remove them from other user's lists
      if (removed) {
        JMSTopicUserRemoved removedTopicUser =
            new JMSTopicUserRemoved(template.getTopic(), template.getUser());

        // Writes the JMSTopicUserRemoved with a 60 second lease, so
        // listeners have 60 seconds to act on it
        space.write(removedTopicUser, transaction, 1000l * 60l);
      }

      transaction.commit();
    } catch (RemoteException
        | UnusableEntryException
        | TransactionException
        | InterruptedException e) {
      System.err.println(
          "Failed to remove user from topic.  "
              + "User ID: '"
              + user.getId().toString()
              + "' && Topic ID: '"
              + topic.getId().toString()
              + "'");
      e.printStackTrace();
    }
  }