const db = require('../lib/database');
const winston = require('winston');
const _ = require('lodash');

const names = require('../data/words.json');

/**
 * Get the modal element in an array
 *
 * @param {*[]} arr -  Array to find mode of
 * @return {*} Modal element
 */
function mode(arr) {
  return arr.sort((a, b) =>
    arr.filter((v) => v===a).length
    - arr.filter((v) => v===b).length
  ).pop();
}

/**
 * Update the name of a channel based on it's members presence
 *
 * @param {Channel} channel - The channel to be updated
 */
async function updateChannel(channel) {
  // see if this is a channel we're managing
  const channelQuery = await db.pool.query(
    'SELECT * FROM channels WHERE discord_id = $1',
    [channel.id]
  );
  if (channelQuery.rowCount > 0) { // yes, we are managing it
    const channelMembers = channel.members.array();
    let channelName = channelQuery.rows[0].name;
    if (channelMembers.length > 0) {
      const presences = channelMembers.map((m) => m.presence.game)
        .filter((x) => x).map((p) => p.name);
      if (presences.length > 0) {
        const modalGame = mode(presences);
        channelName = `${channelName} (${modalGame})`;
      }
    }
    if (channelName == channel.name) {
      winston.log('debug', 'Skipping update for "%s" (no change in name)',
        channel.name);
    } else {
      setChannelName(channel, channelName);
    }
  }
}

/**
 * Wrapper function to set a channel's name
 *
 * @param {Channel} channel - Channel whose name should be changed
 * @param {string} newName - New name for specified channel
 * @return {Promise} Promise for setting the name
 */
function setChannelName(channel, newName) {
  const oldName = channel.name;
  return channel.setName(newName).then(() => {
    winston.log('info', 'Channel changed from "%s" to "%s"', oldName, newName);
  }).catch((err) => {
    winston.log('error', 'Error changing channel name for channel "%s" (%d)',
      oldName, channel.id);
  });
}

/**
 * Handle provisioning and deprovisioning of temporary channels in a guild based
 * on channel occupancy.
 *
 * @param {Guild} guild - The Guild for which channel management should take
 *   place
 */
async function provisionTemporaryChannels(guild) {
  // first lets figure out if we need a temporary channels
  const managedChannelsQuery = await db.pool.query('SELECT * FROM channels');
  const managedChannels = managedChannelsQuery.rows;
  let emptyManagedChannels = managedChannels.filter((mch) =>
    guild.channels.get(mch.discord_id).members.array().length == 0);

  winston.log('debug', '%s empty managed channels',
    emptyManagedChannels.length);
  if (emptyManagedChannels.length == 0) {
    // we need to make a temporary channel
    const channelName =
      `(T) ${_.sampleSize(names, 2).reduce((pre, cur) => `${pre} ${cur}`)}`;
    winston.log('debug', 'Creating new temporary channel "%s"', channelName);
    // create it in discord
    const newChannel = await guild
      .createChannel(channelName, 'voice');
    // insert it in the db
    await db.createChannel(newChannel);
  } else if (emptyManagedChannels.length > 1) {
    // we might need to delete some temporary channels
    winston.log('debug', 'Scanning for empty temporary channels');
    const emptyTemporaryChannels = emptyManagedChannels
      .filter((mch) => mch.temporary);
    for (let emptyChannel of emptyTemporaryChannels) {
      winston.log('debug', 'Deleting temporary channel %s', emptyChannel.name);
      await guild.channels.get(emptyChannel.discord_id).delete();
      await db.deleteChannel(emptyChannel.discord_id);
      delete managedChannels[managedChannels.indexOf(emptyChannel)];
      // re-run the empty filter and break out if we need to
      emptyManagedChannels = managedChannels.filter(
        (mch) => guild.channels.get(mch.discord_id).members.array().length == 0
      );
      if (emptyManagedChannels.length <= 1) {
        break;
      }
    }
  }
}

/**
 * Handle the voice state update generated by Discord.js
 *
 * @param {GuildMember} before - Member before the update
 * @param {GuildMember} after - Member after the update
 */
function handleVoiceStateUpdate(before, after) {
  // do renaming
  if (before.voiceChannelID != null) {
    updateChannel(
      before.guild.channels.find((ch) => ch.id == before.voiceChannelID)
    );
  }
  if (after.voiceChannelID != null) {
    updateChannel(
      after.guild.channels.find((ch) => ch.id == after.voiceChannelID)
    );
  }

  // do temporary channels
  provisionTemporaryChannels(after.guild);
}

module.exports.handleVoiceStateUpdate = handleVoiceStateUpdate;

@@ -3340,16 +3340,70 @@
 orary);%0A
+    emptyTemporaryChannels.reverse(); // newest first%0A
     for 

Delete newer channels over older channels