package net.knarcraft.stargate.utility;

import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.portal.Gate;
import net.knarcraft.stargate.portal.GateHandler;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.PortalLocation;
import net.knarcraft.stargate.portal.PortalOptions;
import net.knarcraft.stargate.portal.PortalOwner;
import net.knarcraft.stargate.portal.PortalRegistry;
import org.bukkit.ChatColor;
import org.bukkit.World;
import org.bukkit.block.Sign;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;

/**
 * Helper class for saving and loading portal save files
 */
public final class PortalFileHelper {

    private PortalFileHelper() {

    }

    /**
     * Saves all portals for the given world
     *
     * @param world <p>The world to save portals for</p>
     */
    public static void saveAllPortals(World world) {
        Stargate.getStargateConfig().addManagedWorld(world.getName());
        String saveFileLocation = Stargate.getPortalFolder() + "/" + world.getName() + ".db";

        try {
            BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(saveFileLocation, false));

            for (Portal portal : PortalRegistry.getAllPortals()) {
                //Skip portals in other worlds
                String worldName = portal.getWorld().getName();
                if (!worldName.equalsIgnoreCase(world.getName())) {
                    continue;
                }
                //Save the portal
                savePortal(bufferedWriter, portal);
            }

            bufferedWriter.close();
        } catch (Exception e) {
            Stargate.logSevere(String.format("Exception while writing stargates to %s: %s", saveFileLocation, e));
        }
    }

    /**
     * Saves one portal
     *
     * @param bufferedWriter <p>The buffered writer to write to</p>
     * @param portal         <p>The portal to save</p>
     * @throws IOException <p>If unable to write to the buffered writer</p>
     */
    private static void savePortal(BufferedWriter bufferedWriter, Portal portal) throws IOException {
        StringBuilder builder = new StringBuilder();
        BlockLocation button = portal.getStructure().getButton();

        //WARNING: Because of the primitive save format, any change in order will break everything!
        builder.append(portal.getName()).append(':');
        builder.append(portal.getSignLocation().toString()).append(':');
        builder.append((button != null) ? button.toString() : "").append(':');

        //Add removes config values to keep indices consistent
        builder.append(0).append(':');
        builder.append(0).append(':');

        builder.append(portal.getYaw()).append(':');
        builder.append(portal.getTopLeft().toString()).append(':');
        builder.append(portal.getGate().getFilename()).append(':');

        //Only save the destination name if the gate is fixed as it doesn't matter otherwise
        builder.append(portal.getOptions().isFixed() ? portal.getDestinationName() : "").append(':');

        builder.append(portal.getNetwork()).append(':');

        //Name is saved as a fallback if the UUID is unavailable
        builder.append(portal.getOwner().getIdentifier());

        //Save all the portal options
        savePortalOptions(portal, builder);

        bufferedWriter.append(builder.toString());
        bufferedWriter.newLine();
    }

    /**
     * Saves all portal options for the given portal
     *
     * @param portal  <p>The portal to save</p>
     * @param builder <p>The string builder to append to</p>
     */
    private static void savePortalOptions(Portal portal, StringBuilder builder) {
        PortalOptions options = portal.getOptions();
        builder.append(':');
        builder.append(options.isHidden()).append(':');
        builder.append(options.isAlwaysOn()).append(':');
        builder.append(options.isPrivate()).append(':');
        builder.append(portal.getWorld().getName()).append(':');
        builder.append(options.isFree()).append(':');
        builder.append(options.isBackwards()).append(':');
        builder.append(options.isShown()).append(':');
        builder.append(options.isNoNetwork()).append(':');
        builder.append(options.isRandom()).append(':');
        builder.append(options.isBungee());
    }

    /**
     * Loads all portals for the given world
     *
     * @param world <p>The world to load portals for</p>
     * @return <p>True if portals could be loaded</p>
     */
    public static boolean loadAllPortals(World world) {
        String location = Stargate.getPortalFolder();

        File database = new File(location, world.getName() + ".db");

        if (database.exists()) {
            return loadPortals(world, database);
        } else {
            Stargate.logInfo(String.format("{%s} No stargates for world ", world.getName()));
        }
        return false;
    }

    /**
     * Loads all the given portals
     *
     * @param world    <p>The world to load portals for</p>
     * @param database <p>The database file containing the portals</p>
     * @return <p>True if the portals were loaded successfully</p>
     */
    private static boolean loadPortals(World world, File database) {
        int lineIndex = 0;
        try {
            Scanner scanner = new Scanner(database);
            while (scanner.hasNextLine()) {
                //Read the line and do whatever needs to be done
                readPortalLine(scanner, ++lineIndex, world);
            }
            scanner.close();

            //Do necessary tasks after all portals have loaded
            doPostLoadTasks(world);
            return true;
        } catch (Exception e) {
            Stargate.logSevere(String.format("Exception while reading stargates from %s: %d", database.getName(),
                    lineIndex));
            e.printStackTrace();
        }
        return false;
    }

    /**
     * Reads one file line containing information about one portal
     *
     * @param scanner   <p>The scanner to read</p>
     * @param lineIndex <p>The index of the read line</p>
     * @param world     <p>The world for which portals are currently being read</p>
     */
    private static void readPortalLine(Scanner scanner, int lineIndex, World world) {
        String line = scanner.nextLine().trim();

        //Ignore empty and comment lines
        if (line.startsWith("#") || line.isEmpty()) {
            return;
        }

        //Check if the min. required portal data is present
        String[] portalData = line.split(":");
        if (portalData.length < 8) {
            Stargate.logInfo(String.format("Invalid line - %s", lineIndex));
            return;
        }

        //Load the portal defined in the current line
        loadPortal(portalData, world, lineIndex);
    }

    /**
     * Performs tasks which must be run after portals have loaded
     *
     * <p>This will open always on portals, print info about loaded stargates and re-draw portal signs for loaded
     * portals.</p>
     *
     * @param world <p>The world portals have been loaded for</p>
     */
    private static void doPostLoadTasks(World world) {
        //Open any always-on portals. Do this here as it should be more efficient than in the loop.
        PortalHandler.verifyAllPortals();
        int portalCount = PortalRegistry.getAllPortals().size();
        int openCount = PortalHandler.openAlwaysOpenPortals();

        //Print info about loaded stargates so that admins can see if all stargates loaded
        Stargate.logInfo(String.format("{%s} Loaded %d stargates with %d set as always-on", world.getName(),
                portalCount, openCount));

        //Re-draw the signs in case a bug in the config prevented the portal from loading and has been fixed since
        for (Portal portal : PortalRegistry.getAllPortals()) {
            portal.drawSign();
        }
    }

    /**
     * Loads one portal from a data array
     *
     * @param portalData <p>The array describing the portal</p>
     * @param world      <p>The world to create the portal in</p>
     * @param lineIndex  <p>The line index to report in case the user needs to fix an error</p>
     */
    private static void loadPortal(String[] portalData, World world, int lineIndex) {
        //Load min. required portal data
        String name = portalData[0];
        BlockLocation button = (portalData[2].length() > 0) ? new BlockLocation(world, portalData[2]) : null;

        //Load the portal's location
        PortalLocation portalLocation = new PortalLocation();
        portalLocation.setSignLocation(new BlockLocation(world, portalData[1]));
        portalLocation.setYaw(Float.parseFloat(portalData[5]));
        portalLocation.setTopLeft(new BlockLocation(world, portalData[6]));

        //Check if the portal's gate type exists and is loaded
        Gate gate = GateHandler.getGateByName(portalData[7]);
        if (gate == null) {
            //Mark the sign as invalid to reduce some player confusion
            markPortalWithInvalidGate(portalLocation, portalData[7], lineIndex);
            return;
        }

        //Load extra portal data
        String destination = (portalData.length > 8) ? portalData[8] : "";
        String network = (portalData.length > 9 && !portalData[9].isEmpty()) ? portalData[9] : Stargate.getDefaultNetwork();
        String ownerString = (portalData.length > 10) ? portalData[10] : "";

        //Get the owner from the owner string
        PortalOwner owner = new PortalOwner(ownerString);

        //Create the new portal
        Portal portal = new Portal(portalLocation, button, destination, name, network, gate, owner,
                PortalHandler.getPortalOptions(portalData));

        //Register the portal, and close it in case it wasn't properly closed when the server stopped
        PortalHandler.registerPortal(portal);
        portal.getPortalOpener().closePortal(true);
    }

    /**
     * Marks a portal with an invalid gate by changing its sign and writing to the console
     *
     * @param portalLocation <p>The location of the portal with an invalid gate</p>
     * @param gateName       <p>The name of the invalid gate type</p>
     * @param lineIndex      <p>The index of the line the invalid portal was found at</p>
     */
    private static void markPortalWithInvalidGate(PortalLocation portalLocation, String gateName, int lineIndex) {
        Sign sign = (Sign) portalLocation.getSignLocation().getBlock().getState();
        sign.setLine(3, ChatColor.DARK_RED + Stargate.getString("signInvalidGate"));
        sign.update();

        Stargate.logInfo(String.format("Gate layout on line %d does not exist [%s]", lineIndex, gateName));
    }

}