package rip.tilly.bedwars.game;

import io.netty.util.internal.ConcurrentSet;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.github.paperspigot.Title;
import rip.tilly.bedwars.BedWars;
import rip.tilly.bedwars.game.arena.Arena;
import rip.tilly.bedwars.game.arena.CopiedArena;
import rip.tilly.bedwars.utils.CC;
import rip.tilly.bedwars.utils.TimeUtils;

import java.util.*;
import java.util.stream.Stream;

@Getter
@Setter
public class Game {

    private final BedWars plugin = BedWars.getInstance();

    private final Set<Entity> entitiesToRemove = new ConcurrentSet<>();
    private final Set<Location> placedBlocksLocations = new ConcurrentSet<>();
    private final Set<UUID> spectators = new ConcurrentSet<>();
    private final Set<Integer> runnables = new HashSet<>();

    private final List<GameTeam> teams;
    private final Arena arena;
    private final GameType gameType;

    private final UUID gameId = UUID.randomUUID();

    private CopiedArena copiedArena;
    private GameState gameState = GameState.STARTING;
    private int countdown = 6;
    private int durationTimer;
    private int winningTeamId;

    public Game(Arena arena, GameType gameType, GameTeam... teams) {
        this.arena = arena;
        this.gameType = gameType;
        this.teams = Arrays.asList(teams);
    }

    public GameTeam getTeamByName(String name) {
        return this.teams.stream().filter(team -> team.getPlayerTeam().getName().equalsIgnoreCase(name)).findFirst().orElse(null);
    }

    public String getDuration() {
        return TimeUtils.formatIntoMMSS(durationTimer);
    }

    public void addEntityToRemove(Entity entity) {
        this.entitiesToRemove.add(entity);
    }

    public void removeEntityToRemove(Entity entity) {
        this.entitiesToRemove.remove(entity);
    }

    public void clearEntitiesToRemove() {
        this.entitiesToRemove.clear();
    }

    public void broadcastTitle(String message, String subMessage) {
        Title title = new Title(CC.translate(message), CC.translate(subMessage), 5, 20, 5);
        this.teams.forEach(team -> team.playingPlayers().forEach(player -> player.sendTitle(title)));
    }

    public void broadcast(String message) {
        this.teams.forEach(team -> team.playingPlayers().forEach(player -> player.sendMessage(CC.translate(message))));
    }

    public void broadcastSound(Sound sound) {
        this.teams.forEach(team -> team.playingPlayers().forEach(player -> player.playSound(player.getLocation(), sound, 10, 1)));
    }

    public void broadcastWithSound(String message, Sound sound) {
        this.teams.forEach(team -> team.playingPlayers().forEach(player -> {
            player.sendMessage(CC.translate(message));
            player.playSound(player.getLocation(), sound, 10, 1);
        }));
    }

    public int decrementCountdown() {
        return --this.countdown;
    }

    public void incrementDuration() {
        ++this.durationTimer;
    }

    public boolean isPartyMatch() {
        return (this.teams.get(0).getAllPlayers().size() >= 2 || this.teams.get(1).getAllPlayers().size() >= 2);
    }

    public void addSpectator(UUID uuid) {
        this.spectators.add(uuid);
    }

    public void removeSpectator(UUID uuid) {
        this.spectators.remove(uuid);
    }

    public Stream<Player> spectatorPlayers() {
        return this.spectators.stream().map(this.plugin.getServer()::getPlayer).filter(Objects::nonNull);
    }

    public void addRunnable(int id) {
        this.runnables.add(id);
    }

    public void addPlacedBlock(Block block) {
        this.placedBlocksLocations.add(block.getLocation());
    }

    public void removePlacedBlock(Block block) {
        this.placedBlocksLocations.remove(block.getLocation());
    }

    public boolean isPlaceable(Location location, Game game) {
        double minX = game.getCopiedArena().getMin().getX();
        double minZ = game.getCopiedArena().getMin().getZ();
        double maxX = game.getCopiedArena().getMax().getX();
        double maxZ = game.getCopiedArena().getMax().getZ();

        if (minX > maxX) {
            double lastMinX = minX;
            minX = maxX;
            maxX = lastMinX;
        }

        if (minZ > maxZ) {
            double lastMinZ = minZ;
            minZ = maxZ;
            maxZ = lastMinZ;
        }

        if (location.getX() >= minX && location.getX() <= maxX && location.getZ() >= minZ && location.getZ() <= maxZ) {
            return true;
        }

        return false;
    }

    public boolean isInside(Location location, Game game) {
        double aMinX = game.getCopiedArena().getTeamAmin().getX();
        double aMinZ = game.getCopiedArena().getTeamAmin().getZ();
        double aMaxX = game.getCopiedArena().getTeamAmax().getX();
        double aMaxZ = game.getCopiedArena().getTeamAmax().getZ();

        if (aMinX > aMaxX) {
            double lastMinX = aMinX;
            aMinX = aMaxX;
            aMaxX = lastMinX;
        }

        if (aMinZ > aMaxZ) {
            double lastMinZ = aMinZ;
            aMinZ = aMaxZ;
            aMaxZ = lastMinZ;
        }

        if (location.getX() >= aMinX && location.getX() <= aMaxX && location.getZ() >= aMinZ && location.getZ() <= aMaxZ) {
            return true;
        }

        double bMinX = game.getCopiedArena().getTeamBmin().getX();
        double bMinZ = game.getCopiedArena().getTeamBmin().getZ();
        double bMaxX = game.getCopiedArena().getTeamBmax().getX();
        double bMaxZ = game.getCopiedArena().getTeamBmax().getZ();

        if (bMinX > bMaxX) {
            double lastMinX = bMinX;
            bMinX = bMaxX;
            bMaxX = lastMinX;
        }

        if (bMinZ > bMaxZ) {
            double lastMinZ = bMinZ;
            bMinZ = bMaxZ;
            bMaxZ = lastMinZ;
        }

        if (location.getX() >= bMinX && location.getX() <= bMaxX && location.getZ() >= bMinZ && location.getZ() <= bMaxZ) {
            return true;
        }

        return false;
    }

    public boolean isBreakable(Block block) {
        if (placedBlocksLocations.contains(block.getLocation())) {
            return true;
        }

        Material material = block.getType();
        switch (material) {
            case BED:
            case BED_BLOCK:
                return true;
        }

        return false;
    }
}
