package com.dvgaming.spigotmc.plots;

import com.dvgaming.common.PersistData;
import com.dvgaming.spigotmc.common.SpecialItem;
import com.dvgaming.spigotmc.common.check.CheckedAction;
import com.dvgaming.spigotmc.common.command.Command;
import com.dvgaming.spigotmc.common.command.CommandGroup;
import com.dvgaming.spigotmc.common.command.CommandHandler;
import com.dvgaming.spigotmc.common.message.ColoredString;
import com.dvgaming.spigotmc.common.message.Message;
import com.dvgaming.spigotmc.common.message.Messenger;
import com.mewin.WGRegionEvents.events.RegionLeftEvent;
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
import com.sk89q.worldguard.protection.managers.storage.StorageException;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.enchantment.PrepareItemEnchantEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.entity.VillagerAcquireTradeEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.inventory.PrepareAnvilEvent;
import org.bukkit.event.inventory.PrepareItemCraftEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.TabCompleteEvent;
import org.bukkit.event.world.WorldSaveEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.MerchantInventory;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.projectiles.ProjectileSource;

import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;

/**
 * The Plots plugin.
 */
public class PlotsPlugin extends JavaPlugin implements Listener, PersistData {
    private ResidentManager residentManager;
    private PlotManager plotManager;
    private SettlementManager settlementManager;

    private WorldGuardPlugin worldGuardPlugin;
    private final Set<World> worldGuardSavesScheduled = new HashSet<>();

    private DynmapExtension dynmapExtension;

    final Set<EntityType> hostileMobs = new HashSet<>();

    private SpecialItem prosperityItem;
    private SpecialItem wandItem;

    private long antiLootDuration;

    private Messenger messenger;

    private final CommandHandler commandHandler = new CommandGroup(
            new CommandGroup("plot", "/plot <create|delete|wand>",
                    new Command("create", 0, "/plot create", new PlotCreateExecutor(this)),
                    new Command("delete", 0, "/plot delete", new PlotDeleteExecutor(this)),
                    new Command("wand", 0, "/plot wand [name]", new PlotWandExecutor(this)),
                    new Command("cheat", 1, "/plot cheat <amount> [name]", new PlotCheatExecutor(this)),
                    new Command("debug", 0, "/debug", (sender, args) -> {
                    })
            ),
            new CommandGroup("settlement", "/settlement <center|rename|treasurer>",
                    new Command("center", 0, "/settlement center", new SettlementCenterExecutor(this)),
                    new Command("rename", 1, "/settlement rename <new name>", new SettlementRenameExecutor(this)),
                    new Command("treasurer", 0, "/settlement treasurer", new SettlementTreasurerExecutor(this))
            )
    );

    @Override
    public void onEnable() {
        // Get dependencies
        final PluginManager pluginManager = getServer().getPluginManager();
        worldGuardPlugin = (WorldGuardPlugin) pluginManager.getPlugin("WorldGuard");

        // Load config
        saveDefaultConfig();

        final ConfigurationSection config = getConfig();

        final ConfigurationSection wandConfig = config.getConfigurationSection("wand");
        wandItem = new SpecialItem(
                Material.valueOf(wandConfig.getString("itemType", "STICK").toUpperCase()),
                wandConfig.getString("itemName", "Plot Wand"),
                Enchantment.DURABILITY, 1);

        final ConfigurationSection prospConfig = config.getConfigurationSection("prosperity");
        prosperityItem = new SpecialItem(
                Material.valueOf(prospConfig.getString("itemType", "PAPER").toUpperCase()),
                prospConfig.getString("itemName", "Prosperity"));

        final ConfigurationSection exploitConfig = config.getConfigurationSection("exploit");
        antiLootDuration = exploitConfig.getLong("antiLootDuration", 60L);

        config.getStringList("hostileMobs").forEach(type ->
                hostileMobs.add(EntityType.valueOf(type.toUpperCase())));

        // Create messenger
        messenger = new Messenger(getServer(),
                new ColoredString("[Plots] ", ChatColor.YELLOW));

        // Create managers
        residentManager = new ResidentManager(this);
        plotManager = new PlotManager(this);
        settlementManager = new SettlementManager(this);
        new VillageScanner(this); // schedules a repeating task

        // Extensions
        final Plugin dynmapPlugin = pluginManager.getPlugin("dynmap");
        if (dynmapPlugin != null) {
            getLogger().info("Dynmap extension active!");
            dynmapExtension = new DynmapExtension(this, dynmapPlugin);
        }

        // Register event listener
        pluginManager.registerEvents(this, this);
    }

    @Override
    public void onDisable() {
        persist();

        if (dynmapExtension != null) {
            dynmapExtension.disable();
        }
    }

    @Override
    public boolean onCommand(
            CommandSender sender,
            org.bukkit.command.Command command,
            String label,
            String[] args) {

        final boolean result = commandHandler.handleCommand(sender, command, args);
        persist();
        return result;
    }

    @SuppressWarnings("unused")
    @EventHandler
    public void onEntityDeath(EntityDeathEvent e) {
        boolean disableDrops = false;
        final Entity entity = e.getEntity();

        if (isHostile(entity)) {
            // check if hostile mob was killed in or from a settlement
            final Settlement deathSettlement = settlementManager.getSettlementAt(
                    entity.getWorld(), entity.getLocation());

            if (deathSettlement != null) {
                // mob was killed in a settlement
                disableDrops = true;
            } else {
                // check killer
                final EntityDamageEvent lastDamage = entity.getLastDamageCause();
                if (lastDamage instanceof EntityDamageByEntityEvent) {
                    Entity killer = ((EntityDamageByEntityEvent) lastDamage).getDamager();
                    if (killer instanceof Projectile) {
                        final ProjectileSource shooter = ((Projectile) killer).getShooter();
                        killer = (shooter instanceof Entity) ? (Entity) shooter : null;
                    }

                    if (killer != null) {
                        final Settlement killerSettlement = settlementManager.getSettlementAt(
                                killer.getWorld(), killer.getLocation());

                        if (killerSettlement != null) {
                            disableDrops = true;
                        } else if (killer instanceof Player) {
                            final Resident resident = residentManager.getResident(killer.getUniqueId());
                            if (System.currentTimeMillis() - resident.antiLootTime < antiLootDuration * 1000L) {
                                disableDrops = true;
                            }
                        }
                    }
                }
            }
        }

        if (disableDrops) {
            e.getDrops().clear();
            e.setDroppedExp(0);
        }
    }

    @SuppressWarnings("unused")
    @EventHandler
    public void onInventoryClose(InventoryCloseEvent e) {
        final Inventory inv = e.getInventory();
        if (inv instanceof MerchantInventory) {
            final Settlement settlement = settlementManager.getTreasurerSettlement(
                    (Villager) inv.getHolder());

            if (settlement != null) {
                final Resident resident = residentManager.getResident(e.getPlayer());
                int prosperity = 0;

                final Inventory playerInv = e.getPlayer().getInventory();
                for (int i = 0; i < playerInv.getSize(); i++) {
                    final ItemStack item = playerInv.getItem(i);
                    if (prosperityItem.matches(item)) {
                        prosperity += item.getAmount();
                        playerInv.setItem(i, null); // remove from inventory
                    }
                }

                if(prosperity > 0) {
                    settlement.setProsperity(settlement.getProsperity() + prosperity);
                    settlementManager.schedulePersist();
                    persist();

                    messenger.send(resident.getPlayer(), Message.success(
                            "You have acquired ",
                            new ColoredString(Integer.toString(prosperity), ChatColor.YELLOW),
                            " prosperity for ", settlement, "!"));
                }
            }
        }
    }

    @SuppressWarnings("unused")
    @EventHandler
    public void onInventoryOpen(InventoryOpenEvent e) {
        final Inventory inv = e.getInventory();
        if (e.getInventory() instanceof MerchantInventory) {
            final Villager villager = (Villager) inv.getHolder();
            final Settlement settlement = settlementManager.getTreasurerSettlement(villager);

            if (settlement != null) {
                final Resident resident = residentManager.getResident(e.getPlayer());
                if (resident != null && CheckedAction.check(resident.getPlayer(), messenger,
                        settlement.new ResidentCheck(resident))) {

                    messenger.send(resident.getPlayer(), Message.info(
                            "You are trading for the prosperity of ", settlement, "."));
                } else {
                    e.setCancelled(true);
                }
            }
        }
    }

    @SuppressWarnings("unused")
    @EventHandler
    public void onPlayerInteract(PlayerInteractEvent e) {
        final ItemStack item = e.getItem();
        if (wandItem.matches(item)) {
            final Player player = e.getPlayer();
            final Resident r = residentManager.getResident(player);
            final Block block = e.getClickedBlock();

            // Player used wand
            switch (e.getAction()) {
                case LEFT_CLICK_BLOCK:
                    r.setPivot(block);
                    r.setSelection(null);
                    messenger.send(player, Message.info(
                            "Pivot set to (X=" + block.getX() + ", Z=" + block.getZ() + ")"));
                    break;

                case RIGHT_CLICK_BLOCK:
                    if (r.getPivot() != null) {
                        r.setSelection(block);

                        messenger.send(player, Message.info(
                                "Selected area: " +
                                        r.getSelectionSizeX() +
                                        "x" + r.getSelectionSizeZ() +
                                        " = " + r.getSelectionArea() + " blocks."));
                    } else {
                        messenger.send(player, Message.error("Select a pivot block first!"));
                    }
                    break;
            }

            // cancel default behaviour
            e.setCancelled(true);
        }
    }

    @SuppressWarnings("unused")
    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent e) {
        final Player player = e.getPlayer();
        residentManager.getResident(player).setPlayer(player);
        persist();
    }

    @SuppressWarnings("unused")
    @EventHandler
    public void onPlayerQuit(PlayerQuitEvent e) {
        final Player player = e.getPlayer();
        residentManager.getResident(player).setPlayer(null);
    }

    @SuppressWarnings("unused")
    @EventHandler
    public void onPrepareAnvil(PrepareAnvilEvent e) {
        if (wandItem.contained(e.getInventory())) {
            // using the wand on an anvil is not possible
            e.setResult(null);
        }
    }

    @SuppressWarnings("unused")
    @EventHandler
    public void onPrepareItemCraft(PrepareItemCraftEvent e) {
        if (wandItem.contained(e.getInventory())) {
            // crafting using the wand is not possible
            e.getInventory().setResult(null);
        }
    }

    @SuppressWarnings("unused")
    @EventHandler
    public void onPrepareItemEnchant(PrepareItemEnchantEvent e) {
        if (wandItem.contained(e.getInventory())) {
            // enchanting the wand is not possible
            e.setCancelled(true);
        }
    }

    @SuppressWarnings("unused")
    @EventHandler
    public void onRegionLeft(RegionLeftEvent e) {
        final Settlement settlement = settlementManager.getSettlement(e.getRegion().getId());
        if (settlement != null) {
            // trigger anti-loot for this resident
            final Resident resident = residentManager.getResident(e.getPlayer());
            resident.antiLootTime = System.currentTimeMillis();
        }
    }

    @SuppressWarnings("unused")
    @EventHandler
    public void onTabComplete(TabCompleteEvent e) {
        commandHandler.tabComplete(e.getBuffer(), e.getCompletions());
    }

    @SuppressWarnings("unused")
    @EventHandler
    public void onVillagerAcquireTrade(VillagerAcquireTradeEvent e) {
        final Settlement settlement = settlementManager.getTreasurerSettlement(e.getEntity());
        if (settlement != null) {
            // treasures cannot acquire new trades
            e.setCancelled(true);
        }
    }

    @SuppressWarnings("unused")
    @EventHandler
    public void onWorldSave(WorldSaveEvent e) {
        persist();
    }

    @Override
    public void persist() {
        if(residentManager.isPersistScheduled()) {
            residentManager.persist();
        }

        if(plotManager.isPersistScheduled()) {
            plotManager.persist();
        }

        if(settlementManager.isPersistScheduled()) {
            settlementManager.persist();
        }

        worldGuardSavesScheduled.forEach(world -> {
            try {
                worldGuardPlugin.getRegionManager(world).saveChanges();
            } catch (StorageException ex) {
                getLogger().log(Level.SEVERE, "Failed to save WorldGuard data", ex);
            }
        });
        worldGuardSavesScheduled.clear();
    }

    WorldGuardPlugin getWorldGuardPlugin() {
        return worldGuardPlugin;
    }

    void scheduleWorldGuardSave(World world) {
        worldGuardSavesScheduled.add(world);
    }

    /**
     * Gets the current resident manager.
     *
     * @return the current resident manager.
     */
    public ResidentManager getResidentManager() {
        return residentManager;
    }

    /**
     * Gets the current plot manager.
     *
     * @return the current plot manager.
     */
    public PlotManager getPlotManager() {
        return plotManager;
    }

    /**
     * Gets the current settlement manager.
     *
     * @return the current settlement manager.
     */
    public SettlementManager getSettlementManager() {
        return settlementManager;
    }

    Messenger getMessenger() {
        return messenger;
    }

    /**
     * Tests if the specified entity is considered hostile in this plugin.
     *
     * @param entity the entity in question.
     * @return {@code true} if the entity is considered hostile, {@code false} otherwise.
     */
    public boolean isHostile(Entity entity) {
        return hostileMobs.contains(entity.getType());
    }

    /**
     * Gets the plot wand special item.
     *
     * @return the plot wand special item.
     */
    SpecialItem getWandItem() {
        return wandItem;
    }

    /**
     * Gets the prosperity special item.
     *
     * @return the prosperity special item.
     */
    SpecialItem getProsperityItem() {
        return prosperityItem;
    }
}
