package tc.oc.commons.bukkit.teleport;

import java.util.UUID;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;

import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import tc.oc.api.docs.Server;
import tc.oc.api.docs.virtual.ServerDoc;
import tc.oc.api.users.UserService;
import tc.oc.commons.bukkit.chat.Audiences;
import tc.oc.commons.bukkit.chat.NameStyle;
import tc.oc.commons.bukkit.chat.PlayerComponent;
import tc.oc.commons.bukkit.event.PlayerServerChangeEvent;
import tc.oc.commons.bukkit.format.ServerFormatter;
import tc.oc.commons.bukkit.nick.IdentityProvider;
import tc.oc.commons.core.chat.Audience;
import tc.oc.commons.core.chat.Component;
import tc.oc.commons.core.chat.NullAudience;

@Singleton
public class Teleporter {

    public static final Permission PERMISSION = new Permission("ocn.teleport", PermissionDefault.FALSE);
    public static final Permission PERMISSION_OTHERS = new Permission("ocn.teleport.others", PermissionDefault.OP);
    public static final String CROSS_DATACENTER_PERMISSION = "server.cross-datacenter";

    private final UserService userService;
    private final IdentityProvider identityProvider;
    private final Audiences audiences;
    private final PlayerServerChanger playerServerChanger;
    private final Server localServer;

    @Inject Teleporter(UserService userService, IdentityProvider identityProvider, Audiences audiences, PlayerServerChanger playerServerChanger, Server localServer) {
        this.userService = userService;
        this.identityProvider = identityProvider;
        this.audiences = audiences;
        this.playerServerChanger = playerServerChanger;
        this.localServer = localServer;
    }

    public boolean isConnectable(Server server) {
        return server.online() &&
               server.visibility() != ServerDoc.Visibility.PRIVATE &&
               server.role() != ServerDoc.Role.BUNGEE &&
               server.network().equals(localServer.network()) &&
               server.datacenter().equals(localServer.datacenter());
    }

    public boolean isVisible(Server server) {
        return isConnectable(server) &&
               server.visibility() == ServerDoc.Visibility.PUBLIC;
    }

    public boolean isLocal(@Nullable ServerDoc.Identity destinationServer) {
        return destinationServer == null ? localServer.role() == ServerDoc.Role.LOBBY
                                         : localServer.bungee_name().equals(destinationServer.bungee_name());
    }

    public void showCurrentServer(CommandSender viewer) {
        showCurrentServer(audiences.get(viewer));
    }

    public void showCurrentServer(Audience audience) {
        audience.sendMessage(new Component(
            new TranslatableComponent(
                "command.server.currentServer",
                ServerFormatter.light.nameWithDatacenter(localServer)
            ),
            ChatColor.DARK_PURPLE
        ));
        audience.sendMessage(new Component(new TranslatableComponent("command.server.switchPrompt"), ChatColor.GREEN));
    }

    public void localTeleport(Player traveler, @Nullable Player destinationPlayer) {
        if(destinationPlayer != null && traveler.hasPermission(PERMISSION)) {
            audiences.get(traveler).sendMessage(new Component(
                new TranslatableComponent("command.server.teleporting",
                                          new PlayerComponent(identityProvider.currentIdentity(destinationPlayer), NameStyle.VERBOSE)),
                ChatColor.DARK_PURPLE
            ));
            traveler.teleport(destinationPlayer);
        }
    }

    public void localTeleport(Player traveler, @Nullable UUID destinationPlayer) {
        if(destinationPlayer != null && traveler.hasPermission(PERMISSION)) {
            localTeleport(traveler, traveler.getServer().getPlayer(destinationPlayer));
        }
    }

    public void remoteTeleport(Player traveler, ServerDoc.Identity destinationServer, @Nullable UUID destinationPlayer) {
        if(traveler.hasPermission(PERMISSION)) {
            Player target = destinationPlayer == null ? null : traveler.getServer().getPlayer(destinationPlayer);
            if(target != null) {
                localTeleport(traveler, target);
            } else if (!isLocal(destinationServer)) {
                // TeleportListener will receive this message and call sendToServer
                userService.requestTeleport(traveler.getUniqueId(), destinationServer, destinationPlayer);
            }
        }
    }

    public ListenableFuture<?> remoteTeleport(Player traveler, @Nullable ServerDoc.Identity server) {
        return remoteTeleport(traveler, server, false);
    }

    public ListenableFuture<?> remoteTeleport(Player traveler, @Nullable ServerDoc.Identity server, boolean quiet) {
        return server != null ? remoteTeleport(traveler, server.datacenter(), server.name(), server.bungee_name(), quiet)
                              : sendToLobby(traveler, quiet);
    }

    public ListenableFuture<?> remoteTeleport(Player traveler, @Nullable String datacenter, @Nullable String serverName, @Nullable String bungeeName) {
        return remoteTeleport(traveler, datacenter, serverName, bungeeName, false);
    }

    public ListenableFuture<?> remoteTeleport(Player traveler, @Nullable String datacenter, @Nullable String serverName, @Nullable String bungeeName, boolean quiet) {
        final Audience audience = quiet ? NullAudience.INSTANCE : audiences.get(traveler);

        if(datacenter == null || !traveler.hasPermission(CROSS_DATACENTER_PERMISSION)) {
            datacenter = localServer.datacenter();
        }

        final BaseComponent fullName = ServerFormatter.light.nameWithDatacenter(datacenter, bungeeName, serverName, bungeeName == null);

        if((bungeeName == null && localServer.role() == ServerDoc.Role.LOBBY) ||
           (bungeeName != null && bungeeName.equals(localServer.bungee_name()))) {
            showCurrentServer(audience);
            return Futures.immediateFuture(null);
        }

        PlayerServerChangeEvent event = new PlayerServerChangeEvent(traveler, datacenter, bungeeName, new TranslatableComponent("servers.cannotChange"));
        traveler.getServer().getPluginManager().callEvent(event);

        if(event.isCancelled()) {
            if(event.getCancelMessage() != null) {
                audience.sendWarning(event.getCancelMessage(), false);
            }
            return Futures.immediateCancelledFuture();
        }

        audience.sendMessage(new Component(new TranslatableComponent("command.server.teleporting", fullName), ChatColor.DARK_PURPLE));
        return playerServerChanger.sendPlayerToServer(traveler, bungeeName, quiet);
    }

    public ListenableFuture<?> sendToLobby(Player player, @Nullable String datacenter, boolean quiet) {
        return remoteTeleport(player, datacenter, null, null, quiet);
    }

    public ListenableFuture<?> sendToLobby(Player player, @Nullable String datacenter) {
        return sendToLobby(player, datacenter, false);
    }

    public ListenableFuture<?> sendToLobby(Player player, boolean quiet) {
        return sendToLobby(player, null, quiet);
    }
}
