package org.redcastlemedia.multitallented.civs.menus.people;

import java.util.*;
import java.util.function.Predicate;
import java.util.logging.Level;

import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import org.redcastlemedia.multitallented.civs.Civs;
import org.redcastlemedia.multitallented.civs.localization.LocaleManager;
import org.redcastlemedia.multitallented.civs.civilians.Civilian;
import org.redcastlemedia.multitallented.civs.civilians.CivilianManager;
import org.redcastlemedia.multitallented.civs.items.CVItem;
import org.redcastlemedia.multitallented.civs.menus.CivsMenu;
import org.redcastlemedia.multitallented.civs.menus.CustomMenu;
import org.redcastlemedia.multitallented.civs.menus.MenuIcon;
import org.redcastlemedia.multitallented.civs.menus.MenuManager;
import org.redcastlemedia.multitallented.civs.regions.Region;
import org.redcastlemedia.multitallented.civs.regions.RegionManager;
import org.redcastlemedia.multitallented.civs.towns.Town;
import org.redcastlemedia.multitallented.civs.towns.TownManager;
import org.redcastlemedia.multitallented.civs.util.Constants;

@CivsMenu(name = "people") @SuppressWarnings("unused")
public class PeopleMenu extends CustomMenu {
    @Override
    public Map<String, Object> createData(Civilian civilian, Map<String, String> params) {
        Map<String, Object> data = new HashMap<>();

        if (params.containsKey("page")) {
            data.put("page", Integer.parseInt(params.get("page")));
        } else {
            data.put("page", 0);
        }
        List<Civilian> civilians = new ArrayList<>();
        Map<UUID, String> ranks = null;
        boolean alreadyOnlineFiltered = false;
        boolean invite = !(!params.containsKey("invite") || "false".equals(params.get("invite")));
        if (invite) {
            data.put("invite", true);
        } else {
            Boolean inviteData = (Boolean) MenuManager.getData(civilian.getUuid(), "invite");
            if (inviteData != null && inviteData) {
                invite = true;
                data.put("invite", true);
            }
        }
        Region region = (Region) MenuManager.getData(civilian.getUuid(), Constants.REGION);
        Town town = (Town) MenuManager.getData(civilian.getUuid(), Constants.TOWN);

        if (region != null || params.containsKey(Constants.REGION)) {
            if (region == null) {
                region = RegionManager.getInstance().getRegionById(params.get(Constants.REGION));
            }
            data.put(Constants.REGION, region);
            ranks = region.getRawPeople();
            if (invite) {
                addOnlinePlayers(civilians, region.getRawPeople().keySet());
                Town containingTown = TownManager.getInstance().getTownAt(region.getLocation());
                if (containingTown != null) {
                    for (UUID uuid : containingTown.getRawPeople().keySet()) {
                        if (region.getRawPeople().containsKey(uuid)) {
                            continue;
                        }
                        civilians.add(CivilianManager.getInstance().getCivilian(uuid));
                    }
                }
            } else {
                for (UUID uuid : region.getRawPeople().keySet()) {
                    civilians.add(CivilianManager.getInstance().getCivilian(uuid));
                }
            }
        } else if (town != null || params.containsKey(Constants.TOWN)) {
            if (town == null) {
                town = TownManager.getInstance().getTown(params.get(Constants.TOWN));
            }
            data.put(Constants.TOWN, town);
            ranks = town.getRawPeople();
            if (invite) {
                addOnlinePlayers(civilians, town.getRawPeople().keySet());
            } else {
                for (UUID uuid : town.getRawPeople().keySet()) {
                    civilians.add(CivilianManager.getInstance().getCivilian(uuid));
                }
            }
        } else if (params.containsKey("uuid")) {
            boolean filterOnline = params.containsKey("online");
            for (UUID uuid : civilian.getFriends()) {
                if (filterOnline && Bukkit.getPlayer(uuid) == null) {
                    continue;
                }
                civilians.add(CivilianManager.getInstance().getCivilian(uuid));
            }
        } else {
            alreadyOnlineFiltered = true;
            if (params.containsKey("online")) {
                addOnlinePlayers(civilians, new HashSet<>());
            } else {
                civilians.addAll(CivilianManager.getInstance().getCivilians());
            }
        }
        if (!alreadyOnlineFiltered && params.containsKey("online") && "true".equals(params.get("online"))) {
            civilians.removeIf(new Predicate<Civilian>() {
                @Override
                public boolean test(Civilian civilian) {
                    return Bukkit.getPlayer(civilian.getUuid()) != null;
                }
            });
        }
        if (params.containsKey("sort")) {
            data.put("sort", params.get("sort"));
            if ("points".equals(params.get("sort"))) {
                pointsSort(civilians);
            } else if ("rank".equals(params.get("sort"))) {
                rankSort(civilians, ranks);
            } else {
                alphabeticalSort(civilians);
            }
        } else {
            alphabeticalSort(civilians);
            data.put("sort", "alphabetical");
        }
        data.put("civilians", civilians);
        data.put("civMap", new HashMap<ItemStack, UUID>());
        int maxPage = (int) Math.ceil((double) civilians.size() / (double) itemsPerPage.get("people"));
        maxPage = maxPage > 0 ? maxPage - 1 : 0;
        data.put("maxPage", maxPage);

        return data;
    }

    private void rankSort(List<Civilian> civilians, Map<UUID, String> ranks) {
        if (ranks == null || ranks.isEmpty()) {
            return;
        }
        civilians.sort(new Comparator<Civilian>() {
            @Override
            public int compare(Civilian o1, Civilian o2) {
                if (!ranks.containsKey(o1.getUuid()) ||
                        !ranks.containsKey(o2.getUuid())) {
                    return 0;
                }
                return Integer.compare(rankWeight(ranks.get(o1.getUuid())),
                        rankWeight(ranks.get(o2.getUuid())));
            }
        });
    }
    private int rankWeight(String rank) {
        if (rank.contains(Constants.OWNER)) {
            return 100;
        } else if (rank.contains("member")) {
            return 50;
        } else if (rank.contains("ally")) {
            return 25;
        } else {
            return 0;
        }
    }

    private void pointsSort(List<Civilian> civilians) {
        civilians.sort(new Comparator<Civilian>() {
            @Override
            public int compare(Civilian o1, Civilian o2) {
                return Double.compare(o1.getPoints(), o2.getPoints());
            }
        });
    }

    private void alphabeticalSort(List<Civilian> civilians) {
        civilians.sort(new Comparator<Civilian>() {
            @Override
            public int compare(Civilian civilian1, Civilian civilian2) {
                OfflinePlayer offlinePlayer1 = Bukkit.getOfflinePlayer(civilian1.getUuid());
                OfflinePlayer offlinePlayer2 = Bukkit.getOfflinePlayer(civilian2.getUuid());
                try {
                    return offlinePlayer1.getName().compareTo(offlinePlayer2.getName());
                } catch (Exception e) {
                    Object[] args = { Civs.NAME, offlinePlayer1.getName(), offlinePlayer2.getName()};
                    Civs.logger.log(Level.WARNING, "{0} Failed to compare name {1} with {2}", args);
                    return 0;
                }
            }
        });
    }

    private void addOnlinePlayers(List<Civilian> civilianList, Set<UUID> blacklist) {
        for (Player player : Bukkit.getOnlinePlayers()) {
            if (blacklist.contains(player.getUniqueId())) {
                continue;
            }
            civilianList.add(CivilianManager.getInstance().getCivilian(player.getUniqueId()));
        }
    }

    @Override @SuppressWarnings("unchecked")
    public ItemStack createItemStack(Civilian civilian, MenuIcon menuIcon, int count) {
        String sort = (String) MenuManager.getData(civilian.getUuid(), "sort");
        if ("sort-rank".equals(menuIcon.getKey())) {
            if ("rank".equals(sort)) {
                return new ItemStack(Material.AIR);
            }
            Boolean invite = (Boolean) MenuManager.getData(civilian.getUuid(), "invite");
            if (invite != null && invite) {
                return new ItemStack(Material.AIR);
            }
            if (MenuManager.getData(civilian.getUuid(), Constants.REGION) == null &&
                    MenuManager.getData(civilian.getUuid(), Constants.TOWN) == null) {
                return new ItemStack(Material.AIR);
            }
        } else if ("sort-alphabetical".equals(menuIcon.getKey())) {
            if ("alphabetical".equals(sort)) {
                return new ItemStack(Material.AIR);
            }
        } else if ("sort-points".equals(menuIcon.getKey())) {
            if ("points".equals(sort)) {
                return new ItemStack(Material.AIR);
            }
        } else if ("people".equals(menuIcon.getKey())) {
            List<Civilian> civilians = (List<Civilian>) MenuManager.getData(civilian.getUuid(), "civilians");
            int page = (int) MenuManager.getData(civilian.getUuid(), "page");
            int startIndex = page * menuIcon.getIndex().size();
            if (civilians.size() <= startIndex + count) {
                return new ItemStack(Material.AIR);
            }
            Civilian currentCivilian = civilians.get(startIndex + count);
            Player currentPlayer = Bukkit.getPlayer(civilian.getUuid());
            OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(currentCivilian.getUuid());
            if (Civs.perm != null && Civs.perm.playerHas(currentPlayer.getWorld().getName(), offlinePlayer, Constants.PVP_EXEMPT_PERMISSION)) {
                return new ItemStack(Material.AIR);
            }
            Player player = null;
            if (offlinePlayer.isOnline()) {
                player = (Player) offlinePlayer;
            }
            CVItem cvItem = new CVItem(Material.PLAYER_HEAD, 1);
            cvItem.setDisplayName(offlinePlayer.getName());
            if (MenuManager.getData(civilian.getUuid(), Constants.REGION) != null) {
                Region region = (Region) MenuManager.getData(civilian.getUuid(), Constants.REGION);
                addRank(civilian.getLocale(), cvItem, region.getRawPeople().get(offlinePlayer.getUniqueId()));
            } else if ((MenuManager.getData(civilian.getUuid(), Constants.TOWN) != null)) {
                Town town = (Town) MenuManager.getData(civilian.getUuid(), Constants.TOWN);
                addRank(civilian.getLocale(), cvItem, town.getRawPeople().get(offlinePlayer.getUniqueId()));
            }
            ItemStack itemStack = cvItem.createItemStack();
            if (player != null) {
                SkullMeta skullMeta = (SkullMeta) itemStack.getItemMeta();
                skullMeta.setOwningPlayer(player);
                itemStack.setItemMeta(skullMeta);
            }
            ((HashMap<ItemStack, UUID>) MenuManager.getData(civilian.getUuid(), "civMap")).put(itemStack, offlinePlayer.getUniqueId());
            putActions(civilian, menuIcon, itemStack, count);
            return itemStack;
        }
        return super.createItemStack(civilian, menuIcon, count);
    }

    private void addRank(String locale, CVItem cvItem, String ranks) {
        if (ranks == null || ranks.isEmpty()) {
            return;
        }
        if (ranks.contains(Constants.OWNER)) {
            cvItem.getLore().add(LocaleManager.getInstance().getTranslation(locale, Constants.OWNER));
        }
        if (ranks.contains("member")) {
            cvItem.getLore().add(LocaleManager.getInstance().getTranslation(locale, "member"));
        }
        if (ranks.contains("recruiter")) {
            cvItem.getLore().add(LocaleManager.getInstance().getTranslation(locale, "recruiter"));
        }
        if (ranks.contains("ally")) {
            cvItem.getLore().add(LocaleManager.getInstance().getTranslation(locale, "guest"));
        }
    }

    @Override @SuppressWarnings("unchecked")
    public boolean doActionAndCancel(Civilian civilian, String actionString, ItemStack clickedItem) {
        if ("take-action".equals(actionString)) {
            UUID uuid = ((HashMap<ItemStack, UUID>) MenuManager.getData(civilian.getUuid(), "civMap")).get(clickedItem);
            Region region = (Region) MenuManager.getData(civilian.getUuid(), Constants.REGION);
            Town town = (Town) MenuManager.getData(civilian.getUuid(), Constants.TOWN);
            Player player = Bukkit.getPlayer(civilian.getUuid());
            Boolean invite = (Boolean) MenuManager.getData(civilian.getUuid(), "invite");
            if (region != null) {
                if (invite != null && invite) {
                    player.performCommand("cv add " + clickedItem.getItemMeta().getDisplayName() + " " + region.getId());
                } else {
                    HashMap<String, String> params = new HashMap<>();
                    params.put(Constants.REGION, region.getId());
                    params.put("uuid", uuid.toString());
                    MenuManager.getInstance().openMenu(player, "member-action", params);
                }
            } else if (town != null) {
                if (invite != null && invite) {
                    player.performCommand("cv invite " + clickedItem.getItemMeta().getDisplayName() + " " + town.getName());
                } else {
                    HashMap<String, String> params = new HashMap<>();
                    params.put(Constants.TOWN, town.getName());
                    params.put("uuid", uuid.toString());
                    MenuManager.getInstance().openMenu(player, "member-action", params);
                }
            } else {
                HashMap<String, String> params = new HashMap<>();
                params.put("uuid", uuid.toString());
                MenuManager.getInstance().openMenu(player, "player", params);
            }
            return true;
        }
        return super.doActionAndCancel(civilian, actionString, clickedItem);
    }
}