/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package works.chatterbox.chatterbox.wrappers;

import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import ninja.leaping.configurate.ConfigurationNode;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import works.chatterbox.chatterbox.Chatterbox;
import works.chatterbox.chatterbox.channels.Channel;
import works.chatterbox.chatterbox.titles.Titles;

import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;

public class UUIDCPlayer implements CPlayer {

    private final UUID uuid;
    private final Set<Channel> joinedChannels = Sets.newHashSet();
    private final Chatterbox chatterbox;
    private Channel mainChannel;

    public UUIDCPlayer(@NotNull final UUID uuid, @NotNull final Chatterbox chatterbox) {
        Preconditions.checkNotNull(uuid, "uuid was null");
        Preconditions.checkNotNull(chatterbox, "chatterbox was null");
        this.uuid = uuid;
        this.chatterbox = chatterbox;
    }

    /**
     * Returns {@code thing} if it is not null. If it is null, {@code other} will be returned, which should not be null.
     *
     * @param thing Thing to return if it isn't null
     * @param other Thing to return if {@code thing} is null (not null)
     * @return {@code thing} or {@code other}
     */
    private <T> T or(@Nullable final T thing, @NotNull final T other) {
        Preconditions.checkNotNull(other, "other was null");
        return thing == null ? other : thing;
    }

    /**
     * {@inheritDoc}
     * <p>Note: the set returned is immutable.
     */
    @NotNull
    @Override
    public Set<Channel> getChannels() {
        return ImmutableSet.copyOf(this.joinedChannels);
    }

    @NotNull
    @Override
    public Channel getMainChannel() {
        return this.or(this.mainChannel, this.chatterbox.getAPI().getChannelAPI().getDefaultChannel());
    }

    @Override
    public void setMainChannel(@NotNull final Channel channel) {
        Preconditions.checkNotNull(channel, "channel was null");
        this.mainChannel = channel;
    }

    @NotNull
    @Override
    public OfflinePlayer getOfflinePlayer() {
        return this.chatterbox.getServer().getOfflinePlayer(this.getUUID());
    }

    @Nullable
    @Override
    public Player getPlayer() {
        return this.chatterbox.getServer().getPlayer(this.getUUID());
    }

    @NotNull
    @Override
    public Titles getTitles() {
        return this.chatterbox.getAPI().getTitleAPI().getTitles(this.getUUID());
    }

    @Override
    public void ifOnline(@NotNull final Consumer<Player> function) {
        Preconditions.checkNotNull(function, "function was null");
        final Player player = this.getPlayer();
        if (player == null) return;
        function.accept(player);
    }

    @Override
    public boolean isOnline() {
        return this.getPlayer() != null;
    }

    @Override
    public boolean joinChannel(@NotNull final Channel channel) {
        Preconditions.checkNotNull(channel, "channel was null");
        if (this.joinedChannels.contains(channel)) return false;
        if (!channel.addMember(this)) return false;
        if (this.joinedChannels.isEmpty()) {
            this.setMainChannel(channel);
        }
        this.joinedChannels.add(channel);
        return true;
    }

    @Override
    public void joinPreviousChannels() {
        final ConfigurationNode memberships = this.chatterbox.getAPI().getChannelAPI().getMemberships();
        for (final String channelName : this.chatterbox.getAPI().getChannelAPI().getAllChannelNames()) {
            final ConfigurationNode channelNode = memberships.getNode(channelName).getNode(this.getUUID().toString());
            // Filter out channels with memberships without the player
            if (channelNode.isVirtual()) continue;
            // Map the names to channels
            final Channel channel = this.chatterbox.getAPI().getChannelAPI().getChannelByName(channelName);
            // Remove any nulls
            if (channel == null) continue;
            // Join each channel
            this.joinChannel(channel);
            if (channelNode.getLong() < 0L) {
                // If the lastSeenTime is negative, it was the main channel
                this.setMainChannel(channel);
            }
        }
    }

    @Override
    public boolean leaveChannel(@NotNull final Channel channel) {
        Preconditions.checkNotNull(channel, "channel was null");
        if (!this.joinedChannels.contains(channel)) return false;
        if (!channel.removeMember(this)) return false;
        this.joinedChannels.remove(channel);
        if (channel.equals(this.getMainChannel())) {
            this.setMainChannel(this.chatterbox.getAPI().getChannelAPI().getDefaultChannel());
        }
        return true;
    }

    /**
     * Gets the UUID that this CPlayer represents.
     *
     * @return UUID
     */
    @NotNull
    public UUID getUUID() {
        return this.uuid;
    }

    @Override
    public int hashCode() {
        return java.util.Objects.hash(
            this.uuid
        );
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) return true;
        if (o == null || this.getClass() != o.getClass()) return false;
        final UUIDCPlayer player = (UUIDCPlayer) o;
        return java.util.Objects.equals(this.uuid, player.uuid) &&
            java.util.Objects.equals(this.joinedChannels, player.joinedChannels) &&
            java.util.Objects.equals(this.mainChannel, player.mainChannel);
    }

    @Override
    public String toString() {
        return Objects.toStringHelper(this)
            .add("uuid", this.uuid)
            .toString();
    }
}

@@ -4895,63 +4895,8 @@
 se;%0A
-        if (!channel.removeMember(this)) return false;%0A
     
@@ -5086,32 +5086,68 @@
 l());%0A        %7D%0A
+        channel.removeMember(this);%0A
         return t

Move method call down