package leigh.minecraft.link;

import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldguard.WorldGuard;
import com.sk89q.worldguard.protection.managers.RegionManager;
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import com.sk89q.worldguard.protection.regions.RegionContainer;
import leigh.eo.EO;
import leigh.esp.sdk.service.schema.v1.MicroServiceCfgElementDAO;
import leigh.mecha.cred.Credential;
import leigh.mecha.cred.CredentialConnection;
import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;
import leigh.minecraft.link.bukkit.BukkitEventHandler;
import leigh.minecraft.link.bukkit.BukkitReputationEventHandler;
import leigh.minecraft.link.cmd.LCCommandHandler;
import leigh.minecraft.link.faction.Faction;
import leigh.minecraft.link.faction.Reputation;
import leigh.minecraft.link.item.XPBottle;
import leigh.minecraft.link.land.Land;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.eclipse.jetty.util.StringUtil;

import java.io.IOException;
import java.util.Map;
import java.util.UUID;

/**
 * This class implements a Bukkit plugin providing a RESTful API service compatible with the
 * LEIGH&CO Enhanced Services Platform (ESP). The service may be used directly or may be placed
 * onto an ESP grid behind a Kabe server.
 * <p>
 * The purpose of the service is to provide the means to author Minecraft extensions / plugins that
 * run in a distributed fashion independently of the Bukkit server instance. This allows plugins
 * to be added, removed, restarted dynamically without interfering or restarting the Bukkit server,
 * provides insulation for the external services to be immune from server version changes, and opens
 * the opportunity for the external services to be written in any programming language.
 *
 * @author Alex Leigh
 */
public class MinecraftLinkPlugin extends JavaPlugin {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(MinecraftLinkPlugin.class);
    private final LCCommandHandler commandHandler;
    private final BukkitEventHandler eventHandler;
    private final Reputation reputation;
    private final XPBottle xpBottle;
    private int maxLot = 0;
    private final String realmAbv = "ne";
    private final boolean fixLots = false;

    public MinecraftLinkPlugin() throws IOException {
        FileConfiguration cfg = this.getConfig();

        String reputationUrl = cfg.getString("reputationUrl");
        String reputationUser = cfg.getString("reputationUser");
        String reputationPassword = cfg.getString("reputationPassword");

        reputation = new Reputation(new CredentialConnection(reputationUrl,
                new Credential(reputationUser, reputationPassword)), new BukkitReputationEventHandler(),
                new Faction());

        xpBottle = new XPBottle(this);
        eventHandler = new BukkitEventHandler(this, xpBottle);
        commandHandler = new LCCommandHandler(reputation, xpBottle);

        String kabeUrl = cfg.getString("kabeUrl");
        String svcUri = cfg.getString("svcUri");

        if (StringUtil.isEmpty(kabeUrl) || StringUtil.isEmpty(svcUri)) {
            logger.error("kabeUrl and svcUri must be specified in config.yml. Aborting.");
            return;
        }

        EO serviceCfg = MicroServiceCfgElementDAO.create();
        MicroServiceCfgElementDAO.setKabeUrl(serviceCfg, kabeUrl);
        MicroServiceCfgElementDAO.setSrcUri(serviceCfg, svcUri);

        LinkMicroService door = new LinkMicroService(serviceCfg, commandHandler.getPlayerCommandFanout(),
                eventHandler.getChatFanout(), this);

        new Thread(door).start();
    }

    public void onEnable() {
        getLogger().info("MinecraftLink mk1 Enabled");
        saveDefaultConfig();
        FileConfiguration config = getConfig();

        initWorldGuard();

        getLogger().info("Config: " + config);
        Bukkit.getServer().getPluginManager().registerEvents(eventHandler, this);
        getCommand("lc").setExecutor(commandHandler);

        Flight flight = new Flight();

        Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
            @Override
            public void run() {
                flight.tick();
            }
        }, 0L, 10L);
    }

    public Reputation getReputation() {
        return reputation;
    }

    /**
     * Interrogate the WorldGuard plugin to determine what our current lot sequence number is.
     */
    // TODO: Move this into its on specific class now that the plugin does a lot of things
    private void initWorldGuard() {
        WorldGuard wg = WorldGuard.getInstance();
        logger.info("Found WG instance: {}", wg);

        // We just care about the main world at the moment
        World world = Bukkit.getWorld("world");
        RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer();
        RegionManager regions = container.get(BukkitAdapter.adapt(world));

        for (Map.Entry<String, ProtectedRegion> r : regions.getRegions().entrySet()) {
            logger.info("Evaluating lot: {}", r.getKey());

            ProtectedRegion rg = r.getValue();

            if (r.getKey().startsWith("ne-")) {
                int x = Integer.parseInt(r.getKey().substring(3));
                if (x > maxLot) maxLot = x;

                // Compatibility for lots that were created prior to 1.18. We want to expand their Y to reach the new
                // dimensions. We only do this if they were previously 0 or 256 as this is what the landBuy()
                // command is creating.
                if (fixLots && rg instanceof ProtectedCuboidRegion) {
                    BlockVector3 minVec = rg.getMinimumPoint();
                    BlockVector3 maxVec = rg.getMaximumPoint();

                    boolean modified = false;

                    if (minVec.getBlockY() == 0) {
                        logger.info("Correcting Ymin on {}", r.getKey());
                        minVec = BlockVector3.at(minVec.getX(), Land.MIN_Y, minVec.getZ());
                        modified = true;
                    }

                    if (maxVec.getBlockY() == 256) {
                        logger.info("Correcting Ymax on {}", r.getKey());
                        maxVec = BlockVector3.at(maxVec.getX(), Land.MAX_Y, maxVec.getZ());
                        modified = true;
                    }

                    if (modified) {
                        ProtectedCuboidRegion newRegion = new ProtectedCuboidRegion(r.getKey(), minVec, maxVec);
                        newRegion.copyFrom(rg);
                        regions.addRegion(newRegion);
                    }
                }
            }
        }

        logger.info("Located max region: {}", maxLot);
    }

    public void onDisable() {
        getLogger().info("MinecraftLink Disabled");
    }

    /**
     * Send a message to the given player name on the Bukkit server, if they are logged in.
     */
    public void sendMessage(String playerName, String msg) {
        new BukkitRunnable() {
            @Override
            public void run() {
                Player p = Bukkit.getPlayer(playerName);
                logger.info("Found player: {}", p);
                if (p != null) p.sendMessage(msg);
            }
        }.run();
    }

    public void sendMessage(UUID playerId, String msg) {
        new BukkitRunnable() {
            @Override
            public void run() {
                Player p = Bukkit.getPlayer(playerId);
                logger.info("Found player: {}", p);
                if (p != null) p.sendMessage(msg);
            }
        }.run();
    }

    /**
     * Return the next sequence number to create a lot. It is acceptable if the caller does not actually
     * use the sequence number, although one will be "consumed" regardless.
     * <p>
     * MTS: Safe.
     */
    public synchronized int getNewLotNumber() {
        return maxLot++;
    }

    public String getRealmAbv() {
        return realmAbv;
    }
}