package tc.oc.commons.bukkit.commands;

import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;

import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandPermissions;
import com.sk89q.minecraft.util.commands.NestedCommand;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import tc.oc.api.bukkit.users.OnlinePlayers;
import tc.oc.api.util.Permissions;
import tc.oc.commons.bukkit.util.PacketTracer;
import tc.oc.commons.core.commands.Commands;
import tc.oc.commons.core.commands.NestedCommands;

import static tc.oc.commons.bukkit.commands.CommandUtils.getCommandSenderOrSelf;

/**
 * Packet tracing commands
 */
public class TraceCommands implements NestedCommands, Listener {

    private final OnlinePlayers onlinePlayers;
    private final AtomicBoolean traceAll = new AtomicBoolean(false);

    @Inject TraceCommands(OnlinePlayers onlinePlayers) {
        this.onlinePlayers = onlinePlayers;
    }

    @EventHandler
    void onLogin(AsyncClientConnectEvent event) {
        if(traceAll.get()) {
            PacketTracer.start(event.channel(), event.channel().remoteAddress().toString(), Bukkit.getLogger());
        }
    }

    public static class Parent implements Commands {
        @Command(
            aliases = "trace",
            desc = "Packet dumping commands",
            min = 1,
            max = -1
        )
        @CommandPermissions(Permissions.DEVELOPER)
        @NestedCommand(TraceCommands.class)
        public void trace(CommandContext args, CommandSender sender) throws CommandException {}
    }

    @Command(
        aliases = {"on", "start"},
        desc = "Start logging packets",
        min = 0,
        max = 1
    )
    public void start(CommandContext args, CommandSender sender) throws CommandException {
        if(sender instanceof Player || args.argsLength() >= 1) {
            final Player player = (Player) getCommandSenderOrSelf(args, sender, 0);
            if(PacketTracer.start(player, Bukkit.getLogger())) {
                sender.sendMessage("Started packet trace for " + player.getName(sender));
            }
        } else if(traceAll.compareAndSet(false, true)) {
            onlinePlayers.all().forEach(player -> PacketTracer.start(player, Bukkit.getLogger()));
            sender.sendMessage("Started global packet trace");
        }
    }

    @Command(
        aliases = {"off", "stop"},
        desc = "Stop logging packets",
        min = 0,
        max = 1
    )
    public void stop(CommandContext args, CommandSender sender) throws CommandException {
        if(sender instanceof Player || args.argsLength() >= 1) {
            final Player player = (Player) getCommandSenderOrSelf(args, sender, 0);
            if(PacketTracer.stop(player)) {
                sender.sendMessage("Stopped packet trace for " + player.getName(sender));
            }
        } else {
            traceAll.set(false);
            if(onlinePlayers.all().stream().anyMatch(PacketTracer::stop)) {
                sender.sendMessage("Stopped all packet tracing");
            }
        }
    }

    @Command(
        aliases = {"clear", "cl"},
        desc = "Clear all filters",
        min = 0,
        max = -1
    )
    public void clear(CommandContext args, CommandSender sender) throws CommandException {
        PacketTracer.clearFilter();
        sender.sendMessage("Trace filters cleared");
    }

    @Command(
        aliases = {"include", "inc"},
        desc = "Include packets in the trace",
        min = 0,
        max = -1
    )
    public void include(CommandContext args, CommandSender sender) throws CommandException {
        filter(args, sender, true);
    }

    @Command(
        aliases = {"exclude", "ex"},
        desc = "Exclude packets from the trace",
        min = 0,
        max = -1
    )
    public void exclude(CommandContext args, CommandSender sender) throws CommandException {
        filter(args, sender, false);
    }

    private static void filter(CommandContext args, CommandSender sender, boolean include) throws CommandException {
        if(PacketTracer.getDefaultInclude() == include) {
            PacketTracer.clearFilter();
            PacketTracer.setDefaultInclude(!include);
        }

        for(String name : args.getSlice(1)) {
            Class<?> type = PacketTracer.findPacketType(name);
            if(type == null) {
                throw new CommandException("No packet named '" + name + "'");
            }
            PacketTracer.filter(type, include);
            sender.sendMessage((include ? "Including" : "Excluding") + " packet " + type.getSimpleName());
        }
    }
}
