package tc.oc.commons.bukkit.format;

import java.util.Comparator;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Inject;

import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import java.time.Duration;
import java.time.Instant;
import tc.oc.api.docs.virtual.MatchDoc;
import tc.oc.api.docs.virtual.ServerDoc;
import tc.oc.api.minecraft.MinecraftService;
import tc.oc.commons.core.chat.Component;
import tc.oc.commons.core.chat.Components;
import tc.oc.commons.core.formatting.PeriodFormats;

public class ServerFormatter {

    public static Comparator<? super ServerDoc.Identity> proximityOrder(final ServerDoc.Identity server) {
        return (a, b) -> {
            int cmp = Boolean.compare(server.equals(b), server.equals(a));
            if(cmp != 0) return cmp;

            return Boolean.compare(server.datacenter().equals(b.datacenter()), server.datacenter().equals(a.datacenter()));
        };
    }

    public interface Colors {
        ChatColor online();
        ChatColor restarting();
        ChatColor offline();

        ChatColor label();

        ChatColor background();
        ChatColor maxPlayers();
        ChatColor players();
        ChatColor participants();
        ChatColor observers();

        ChatColor matchIdle();
        ChatColor matchStarting();
        ChatColor matchRunning();
        ChatColor matchFinished();

        ChatColor mapName();
    }

    // Picker and chat
    public static final Colors lightColors = new Colors() {
        @Override public ChatColor online() { return ChatColor.YELLOW; }
        @Override public ChatColor restarting() { return ChatColor.BLUE; }
        @Override public ChatColor offline() { return ChatColor.DARK_GRAY; }

        @Override public ChatColor label() { return ChatColor.BLUE; }

        @Override public ChatColor background() { return ChatColor.DARK_GRAY; }
        @Override public ChatColor maxPlayers() { return ChatColor.WHITE; }
        @Override public ChatColor players() { return ChatColor.AQUA; }
        @Override public ChatColor participants() { return ChatColor.GREEN; }
        @Override public ChatColor observers() { return ChatColor.AQUA; }

        @Override public ChatColor matchIdle() { return ChatColor.WHITE; }
        @Override public ChatColor matchStarting() { return ChatColor.GREEN; }
        @Override public ChatColor matchRunning() { return ChatColor.BLUE; }
        @Override public ChatColor matchFinished() { return ChatColor.RED; }

        @Override public ChatColor mapName() { return ChatColor.AQUA; }
    };

    // Signs
    public static final Colors darkColors = new Colors() {
        @Override public ChatColor online() { return ChatColor.BLACK; }
        @Override public ChatColor restarting() { return ChatColor.DARK_GRAY; }
        @Override public ChatColor offline() { return ChatColor.GRAY; }

        @Override public ChatColor label() { return ChatColor.DARK_BLUE; }

        @Override public ChatColor background() { return ChatColor.GRAY; }
        @Override public ChatColor maxPlayers() { return ChatColor.DARK_GRAY; }
        @Override public ChatColor players() { return ChatColor.DARK_AQUA; }
        @Override public ChatColor participants() { return ChatColor.DARK_AQUA; }
        @Override public ChatColor observers() { return ChatColor.DARK_AQUA; }

        @Override public ChatColor matchIdle() { return ChatColor.DARK_GRAY; }
        @Override public ChatColor matchStarting() { return ChatColor.BLUE; }
        @Override public ChatColor matchRunning() { return ChatColor.DARK_GREEN; }
        @Override public ChatColor matchFinished() { return ChatColor.DARK_RED; }

        @Override public ChatColor mapName() { return ChatColor.DARK_GREEN; }
    };

    @Inject static private MinecraftService minecraftService;

    // TODO: figure out an injection-friendly way to do this
    @Deprecated public static final ServerFormatter light = new ServerFormatter(lightColors);
    @Deprecated public static final ServerFormatter dark = new ServerFormatter(darkColors);

    private final Colors colors;
    public Colors colors() { return colors; }

    private ServerFormatter(Colors colors) {
        this.colors = colors;
    }

    public boolean isRestarting(ServerDoc.Listing server) {
        return server.running() && (
            !server.online() || (
                server.restart_queued_at() != null && (
                    server.current_match() == null || server.current_match().end() != null
                )
            )
        );
    }

    public ChatColor statusColor(ServerDoc.Listing server) {
        if(isRestarting(server)) {
            return colors.restarting();
        } else if(server.online()) {
            return colors.online();
        } else {
            return colors.offline();
        }
    }

    public BaseComponent name(ServerDoc.Listing server) {
        return new Component(server.name(), server.online() ? colors.online() : colors.offline(), ChatColor.BOLD);
    }

    public Optional<BaseComponent> description(ServerDoc.Listing server) {
        return server.description() == null ? Optional.empty()
                                            : Optional.of(new Component(new TranslatableComponent(server.description()), ChatColor.DARK_AQUA));
    }

    public BaseComponent onlineStatus(ServerDoc.Listing server) {
        final String key;
        if(isRestarting(server)) {
            key = "servers.restarting";
        } else if(server.online()) {
            key = "servers.online";
        } else {
            key = "servers.offline";
        }
        return new Component(new TranslatableComponent(key), statusColor(server));
    }

    public BaseComponent nameWithDatacenter(ServerDoc.Identity s) {
        return nameWithDatacenter(s.datacenter(), s.bungee_name(), s.name(), s.role() == ServerDoc.Role.LOBBY);
    }

    public BaseComponent nameWithDatacenter(@Nullable String datacenter, @Nullable String bungee, @Nullable String name, boolean lobby) {
        return nameWithDatacenter(minecraftService.getLocalServer(), datacenter, bungee, name, lobby);
    }

    public BaseComponent nameWithDatacenter(ServerDoc.Identity local, @Nullable String datacenter, @Nullable String bungee, @Nullable String name, boolean lobby) {
        Component c = new Component(ChatColor.WHITE);

        if(datacenter == null) {
            datacenter = local.datacenter();
        }

        if(!datacenter.equals(local.datacenter())) {
            c.extra("[").extra(new Component(datacenter, ChatColor.GOLD)).extra("] ");
        }

        Component nameComponent = new Component(ChatColor.GOLD);
        if(name != null) {
            nameComponent.text(name);
        } else {
            nameComponent.extra(new TranslatableComponent("servers.lobby"));
        }

        nameComponent.hoverEvent(HoverEvent.Action.SHOW_TEXT, new TranslatableComponent("tip.connectTo", nameComponent.duplicate()));

        if(lobby) {
            nameComponent.clickEvent(makeLobbyClickEvent());
        } else {
            nameComponent.clickEvent(makeServerClickEvent(bungee));
        }

        c.extra("[").extra(nameComponent).extra("]");

        return c;
    }

    /**
     * [server] [player counts] [match time]
     */
    public BaseComponent compactHeading(ServerDoc.Listing server) {
        if(server.online()) {
            Component c = new Component()
                .extra(new Component(server.name(), ChatColor.WHITE, ChatColor.BOLD))
                .extra(" ")
                .extra(playerCounts(server, true));

            if(server.current_match() != null) {
                c.extra(" ").extra(matchTime(server.current_match()));
            }

            return c;
        } else {
            return new Component(ChatColor.DARK_GRAY)
                .extra(new Component(server.name(), ChatColor.BOLD))
                .extra(" (")
                .extra(new TranslatableComponent("servers.offline"))
                .extra(")");
        }
    }

    public BaseComponent playerCounts(ServerDoc.Listing server, boolean observers) {
        Component c = new Component(colors.background());

        if(server.role() == ServerDoc.Role.PGM) {
            c.extra(new Component(String.valueOf(observers ? server.num_participating() : server.num_online()), colors.participants()));
            c.extra("/");
            c.extra(new Component(String.valueOf(server.max_players()), colors.maxPlayers()));
            if(observers) {
                c.extra(" (");
                c.extra(new Component(String.valueOf(server.num_observing()), colors.observers()));
                c.extra(")");
            }
        } else {
            c.extra(new Component(String.valueOf(server.num_online()), colors.players()));
        }

        return c;
    }

    public ChatColor matchStatusColor(ServerDoc.Status server) {
        final MatchDoc match = server.current_match();

        if(match != null && server.num_online() > 0) {
            if(match.end() != null) {
                return colors.matchFinished();
            } else if(match.start() != null) {
                return colors.matchRunning();
            } else {
                return colors.matchStarting();
            }
        } else {
            return colors.matchIdle();
        }
    }

    public BaseComponent matchTime(MatchDoc doc) {
        Duration time;
        ChatColor color;
        if(doc.start() == null) {
            time = Duration.ZERO;
            color = ChatColor.GOLD;
        } else if(doc.end() == null) {
            time = Duration.between(doc.start(), Instant.now());
            color = ChatColor.GREEN;
        } else {
            time = Duration.between(doc.start(), doc.end());
            color = ChatColor.GOLD;
        }
        return new Component(PeriodFormats.formatColons(time), color);
    }

    public BaseComponent currentMap(ServerDoc.Status server) {
        final MatchDoc match = server.current_match();
        if(match == null || match.map() == null) {
            return Components.blank();
        }

        return new Component(
            new TranslatableComponent(
                "servers.currentMap",
                new Component(match.map().name(), colors.mapName())
            ),
            colors.label()
        );
    }

    public BaseComponent nextMap(ServerDoc.Status server) {
        if(server.next_map() == null) return Components.blank();

        return new Component(
            new TranslatableComponent(
                "servers.nextMap",
                new Component(server.next_map().name(), colors.mapName())
            ),
            colors.label()
        );
    }

    public ClickEvent makeLobbyClickEvent() {
        return new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/hub");
    }

    public ClickEvent makeServerClickEvent(ServerDoc.Identity server) {
        return makeServerClickEvent(server.bungee_name());
    }

    public ClickEvent makeServerClickEvent(String bungee) {
        return new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/server -b " + bungee);
    }
}
