// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
//      Magicbane Emulator Project © 2013 - 2022
//                www.magicbane.com


package engine.server.world;

import engine.InterestManagement.RealmMap;
import engine.InterestManagement.WorldGrid;
import engine.db.archive.DataWarehouse;
import engine.db.handlers.dbNPCHandler;
import engine.db.handlers.dbRuneBaseHandler;
import engine.gameManager.*;
import engine.job.JobContainer;
import engine.job.JobScheduler;
import engine.jobs.LogoutCharacterJob;
import engine.mbEnums;
import engine.mbEnums.DispatchChannel;
import engine.mbEnums.MinionType;
import engine.mbEnums.SupportMsgType;
import engine.mobileAI.Threads.MobAIThread;
import engine.mobileAI.Threads.ReSpawner;
import engine.net.Network;
import engine.net.Protocol;
import engine.net.client.ClientConnection;
import engine.net.client.ClientConnectionManager;
import engine.net.client.msg.UpdateStateMsg;
import engine.net.client.msg.chat.ChatSystemMsg;
import engine.objects.*;
import engine.server.MBServerStatics;
import engine.util.MapLoader;
import engine.util.ThreadUtils;
import engine.workthreads.DisconnectTrashTask;
import engine.workthreads.HourlyJobThread;
import engine.workthreads.PurgeOprhans;
import engine.workthreads.WarehousePushThread;
import org.pmw.tinylog.Configurator;
import org.pmw.tinylog.Level;
import org.pmw.tinylog.Logger;
import org.pmw.tinylog.labelers.TimestampLabeler;
import org.pmw.tinylog.policies.StartupPolicy;
import org.pmw.tinylog.writers.RollingFileWriter;

import java.io.*;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;

import static engine.gameManager.SimulationManager.SERVERHEARTBEAT;
import static java.lang.System.exit;

public class WorldServer {

    public static int worldMapID = Integer.parseInt(ConfigManager.MB_WORLD_MAPID.getValue());
    public static int worldRealmMap = Integer.parseInt(ConfigManager.MB_WORLD_REALMMAP.getValue());

    public static int worldUUID = 1; // Root object in database
    public static mbEnums.AccountStatus worldAccessLevel = mbEnums.AccountStatus.valueOf(ConfigManager.MB_WORLD_ACCESS_LVL.getValue());
    private static LocalDateTime bootTime = LocalDateTime.now();
    public boolean isRunning = false;

    // Member variable declaration

    public WorldServer() {
        super();
    }

    public static void main(String[] args) throws IOException {

        WorldServer worldServer;

        // Configure TinyLogger
        Configurator.defaultConfig()
                .addWriter(new RollingFileWriter("logs/world/world.txt", 30, new TimestampLabeler(), new StartupPolicy()))
                .level(Level.DEBUG)
                .formatPattern("{level} {date:yyyy-MM-dd HH:mm:ss.SSS} [{thread}] {class}.{method}({line}) : {message}")
                .writingThread("main", 2)
                .activate();

        if (ConfigManager.init() == false) {
            Logger.error("ABORT! Missing config entry!");
            return;
        }

        try {

            worldServer = new WorldServer();

            ConfigManager.serverType = mbEnums.ServerType.WORLDSERVER;
            ConfigManager.worldServer = worldServer;

            worldServer.init();

            int retVal = worldServer.exec();

            if (retVal != 0)
                Logger.error(
                        ".exec() returned value: '" + retVal);
            exit(retVal);

        } catch (Exception e) {
            Logger.error(e.getMessage());
            exit(1);
        }
    }

    public static String getUptimeString() {

        String outString = null;
        java.time.Duration uptimeDuration;
        String newLine = System.getProperty("line.separator");

        try {
            outString = "[LUA_UPTIME()]" + newLine;
            uptimeDuration = java.time.Duration.between(LocalDateTime.now(), WorldServer.bootTime);
            long uptimeSeconds = Math.abs(uptimeDuration.getSeconds());
            String uptime = String.format("%d hours %02d minutes %02d seconds", uptimeSeconds / 3600, (uptimeSeconds % 3600) / 60, (uptimeSeconds % 60));
            outString += "uptime: " + uptime;
            outString += " pop: " + SessionManager.getActivePlayerCharacterCount() + " max pop: " + SessionManager._maxPopulation;
        } catch (Exception e) {
            Logger.error("Failed to build string");
        }
        return outString;
    }

    public static void writePopulationFile() {

        int population = SessionManager.getActivePlayerCharacterCount();
        try {


            File populationFile = new File(ConfigManager.DEFAULT_DATA_DIR + ConfigManager.MB_WORLD_NAME.getValue().replaceAll("'", "") + ".pop");
            FileWriter fileWriter;

            try {
                fileWriter = new FileWriter(populationFile, false);
                fileWriter.write(Integer.toString(population));
                fileWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        } catch (Exception e) {
            Logger.error(e);
        }
    }

    private int exec() {

        LocalDateTime nextHeartbeatTime = LocalDateTime.now();
        LocalDateTime nextPopulationFileTime = LocalDateTime.now();
        LocalDateTime nextFlashTrashCheckTime = LocalDateTime.now();
        LocalDateTime nextHourlyJobTime = LocalDateTime.now().withMinute(0).withSecond(0).plusHours(1);
        LocalDateTime nextWareHousePushTime = LocalDateTime.now();

        // Begin execution of main game loop

        this.isRunning = true;

        while (true) {

            if (LocalDateTime.now().isAfter(nextHeartbeatTime)) {
                SERVERHEARTBEAT.tick();
                nextHeartbeatTime = LocalDateTime.now().plusNanos(50000000);
            }

            if (LocalDateTime.now().isAfter(nextPopulationFileTime)) {
                writePopulationFile();
                nextPopulationFileTime = LocalDateTime.now().plusMinutes(1);
            }

            if (LocalDateTime.now().isAfter(nextFlashTrashCheckTime)) {
                processFlashFile();
                processTrashFile();
                nextFlashTrashCheckTime = LocalDateTime.now().plusSeconds(15);
            }

            if (LocalDateTime.now().isAfter(nextHourlyJobTime)) {
                Thread hourlyJobThread = new Thread(new HourlyJobThread());
                hourlyJobThread.setName("hourlyJob");
                hourlyJobThread.start();
                nextHourlyJobTime = LocalDateTime.now().withMinute(0).withSecond(0).plusHours(1);
            }

            if (LocalDateTime.now().isAfter(nextWareHousePushTime)) {
                Thread warehousePushThread = new Thread(new WarehousePushThread());
                warehousePushThread.setName("warehousePush");
                warehousePushThread.start();
                nextWareHousePushTime = LocalDateTime.now().plusMinutes(15);
            }

            ThreadUtils.sleep(50);
        }
    }

    private void initClientConnectionManager() {

        try {

            String name = ConfigManager.MB_WORLD_NAME.getValue();

            if (ConfigManager.MB_EXTERNAL_ADDR.getValue().equals("0.0.0.0")) {

                // Autoconfigure External IP address.  Only used in loginserver but useful
                // here for bootstrap display

                Logger.info("AUTOCONFIG EXTERNAL IP ADDRESS");
                URL whatismyip = new URL("http://checkip.amazonaws.com");

                BufferedReader in = new BufferedReader(new InputStreamReader(
                        whatismyip.openStream()));
                ConfigManager.MB_EXTERNAL_ADDR.setValue(in.readLine());
            }

            if (ConfigManager.MB_BIND_ADDR.getValue().equals("0.0.0.0")) {

                try (final DatagramSocket socket = new DatagramSocket()) {
                    socket.connect(InetAddress.getByName("8.8.8.8"), 10002);
                    ConfigManager.MB_BIND_ADDR.setValue(socket.getLocalAddress().getHostAddress());
                }

            }

            Logger.info("External address: " + ConfigManager.MB_EXTERNAL_ADDR.getValue() + ":" + ConfigManager.MB_WORLD_PORT.getValue());
            Logger.info("Internal address: " + ConfigManager.MB_BIND_ADDR.getValue() + ":" + ConfigManager.MB_WORLD_PORT.getValue());

            InetAddress addy = InetAddress.getByName(ConfigManager.MB_BIND_ADDR.getValue());
            int port = Integer.parseInt(ConfigManager.MB_WORLD_PORT.getValue());

            ClientConnectionManager connectionManager = new ClientConnectionManager(name + ".ClientConnMan", addy,
                    port);
            connectionManager.startup();

        } catch (IOException e) {
            Logger.error("Exception while creating a ClientConnectionManager.");
        }
    }

    private boolean init() {

        Logger.info("MAGICBANE SERVER GREETING:");
        Logger.info(ConfigManager.MB_WORLD_GREETING.getValue());

        Logger.info("Initialize network protocol");
        Protocol.initProtocolLookup();

        Logger.info("Initialize database layer");
        initDatabaselayer();

        Logger.info("Starting network Dispatcher");
        DispatchManager.startMessagePump();

        Logger.info("Setting cross server session behavior");
        SessionManager.setCrossServerBehavior(1); // Sets cross server behavior

        Logger.info("Initializing Errant Guild");
        Guild.getErrantGuild();

        Logger.info("Loading zone template data");
        DbManager.ZoneQueries.LOAD_ALL_ZONE_TEMPLATES();

        Logger.info("Initializing PowersManager.");
        PowersManager.initPowersManager(true);

        Logger.info("Loading granted Skills for Runes");
        DbManager.SkillsBaseQueries.LOAD_ALL_RUNE_SKILLS();

        Logger.info("Loading Player Friends");
        DbManager.PlayerCharacterQueries.LOAD_PLAYER_FRIENDS();

        Logger.info("Loading Building Friends");
        DbManager.BuildingQueries.LOAD_BUILDING_FRIENDS();

        Logger.info("Loading Building Condemned");
        DbManager.BuildingQueries.LOAD_BUILDING_CONDEMNED();

        Logger.info("Loading Barracks Patrol Points");
        DbManager.BuildingQueries.LOAD_BARRACKS_PATROL_POINTS();

        Logger.info("Initializing NPC Profits");
        DbManager.NPCQueries.LOAD_NPC_PROFITS();

        Logger.info("Initializing Petition Table");
        DbManager.PetitionQueries.CREATE_PETITION_TABLE();

        Logger.info("Initializing MeshBounds");
        MeshBounds.InitializeBuildingBounds();

        Logger.info("Loading Item Templates");
        DbManager.ItemQueries.LOAD_ITEM_TEMPLATES();

        Logger.info("Back fill template modTables");
        DbManager.ItemQueries.LOAD_TEMPLATE_MODTABLES();

        Logger.info("Loading PromotionClasses");
        DbManager.PromotionQueries.GET_ALL_PROMOTIONS();

        Logger.info("Loading NPC and Mob Rune Sets");
        NPCManager._runeSetMap = dbNPCHandler.LOAD_RUNES_FOR_NPC_AND_MOBS();

        Logger.info("Loading Booty Sets");
        LootManager._bootySetMap = DbManager.LootQueries.LOAD_BOOTY_TABLES();

        // Load new loot system
        Logger.info("Initializing Loot Manager");
        LootManager.init();

        RuneBaseAttribute.LoadAllAttributes();
        RuneBase.LoadAllRuneBases();
        BaseClass.LoadAllBaseClasses();
        Race.loadAllRaces();
        RuneBaseEffect.LoadRuneBaseEffects();
        Logger.info("Loading MobBases.");
        DbManager.MobBaseQueries.GET_ALL_MOBBASES();

        Logger.info("Loading Rune Powers");
        PowersManager._allRunePowers = dbRuneBaseHandler.LOAD_RUNE_POWERS();

        Logger.info("Loading Rune Skill Adjusts");
        PowersManager._allRuneSkillAdjusts = dbRuneBaseHandler.LOAD_RUNE_SKILL_ADJUSTS();

        Logger.info("Loading item enchants");
        DbManager.LootQueries.LOAD_ENCHANT_VALUES();

        Logger.info("Loading Realms");
        Realm.loadAllRealms();

        Logger.info("Loading RealmMap");
        RealmMap.loadRealmImageMap();

        Logger.info("Loading Kits");
        DbManager.KitQueries.GET_ALL_KITS();

        Logger.info("Loading World Grid");
        WorldGrid.InitializeGridObjects();

        Logger.info("Starting InterestManager.");
        WorldGrid.startLoadJob();

        Logger.info("Loading blueprint data.");
        StaticColliders.loadAllStaticColliders();
        BuildingRegions.loadAllStaticColliders();
        Blueprint.loadAllDoorNumbers();
        Blueprint.loadAllBlueprints();

        Logger.info("Loading Heightmap Pixel data");
        MapLoader.loadAlHeightMaps();

        Logger.info("Loading Race data");
        mbEnums.RaceType.initRaceTypeTables();
        Race.loadAllRaces();

        Logger.info("Loading building slot/stuck location data.");
        BuildingLocation.loadBuildingLocations();

        // Starting before loading of structures/guilds/characters
        // so the database connections are available to write
        // historical data.

        Logger.info("Starting Data Warehouse");
        DataWarehouse.bootStrap();

        Logger.info("Loading Minion Bases.");
        MinionType.InitializeMinions();

        Logger.info("Loading Charter Types.");
        mbEnums.GuildCharterType.init();

        Logger.info("Loading Pirate Names.");
        NPCManager.loadAllPirateNames();

        Logger.info("Loading Support Types");
        SupportMsgType.InitializeSupportMsgType();

        //Load Buildings, Mobs and NPCs for server

        Logger.info("Populating world with objects");

        long start = System.currentTimeMillis();

        Logger.info("Loading all Zones");
        DbManager.ZoneQueries.GET_ALL_ZONES();
        Logger.info("Loading all Buildings");
        DbManager.BuildingQueries.GET_ALL_BUILDINGS();
        Logger.info("Loading all Cities");
        DbManager.CityQueries.GET_ALL_CITIES();
        Logger.info("Loading all NPCs");
        DbManager.NPCQueries.GET_ALL_NPCS();
        Logger.info("Loading all Mobiles");
        DbManager.MobQueries.GET_ALL_MOBS();

        Logger.info("time to load World Objects: " + (System.currentTimeMillis() - start) + " ms");

        // Configure realms for serialization
        // Doing this after the world is loaded

        Logger.info("Configuring realm serialization data");
        Realm.configureAllRealms();

        Logger.info("Loading Mine data.");
        Mine.loadAllMines();

        Logger.info("Loading Shrine data.");
        DbManager.ShrineQueries.LOAD_ALL_SHRINES();

        Logger.info("Initialize Resource type lookup");
        mbEnums.ResourceType.InitializeResourceTypes();

        Logger.info("Loading Warehouse data.");
        DbManager.WarehouseQueries.LOAD_WAREHOUSES();

        Logger.info("Loading Runegate data.");
        Runegate.loadAllRunegates();

        Logger.info("Loading Max Skills for Trainers");
        DbManager.SkillsBaseQueries.LOAD_ALL_MAX_SKILLS_FOR_CONTRACT();

        //pick a startup Hotzone
        ZoneManager.generateAndSetRandomHotzone();

        Logger.info("Loading Player Heraldries");
        DbManager.PlayerCharacterQueries.LOAD_HERALDY();

        Logger.info("Running Heraldry Audit for Deleted Players");
        Heraldry.AuditHeraldry();

        //intiate mob ai thread
        Logger.info("Starting Mob AI Thread");
        MobAIThread.startAIThread();

        Logger.info("World data loaded.");

        //set default accesslevel for server  *** Refactor who two separate variables?
        MBServerStatics.accessLevel = worldAccessLevel;
        Logger.info("Default access level set to " + MBServerStatics.accessLevel);

        Logger.info("Initializing Network");
        Network.init();

        Logger.info("Initializing Client Connection Manager");
        initClientConnectionManager();

        //intiate mob respawn thread
        Logger.info("Starting Mob Respawn Thread");
        ReSpawner.start();

        Logger.info("Loading effect cost maps");
        DbManager.EffectsResourceCostsQueries.LOAD_ALL_COSTMAPS();

        Logger.info("Starting ForgeManager thread");
        ForgeManager.start();

        Logger.info("Restoring NPC workOrders");
        DbManager.WarehouseQueries.LOAD_WORKORDERS();

        // Run maintenance

        MaintenanceManager.dailyMaintenance();

        Logger.info("Starting Orphan Item Purge");
        PurgeOprhans.startPurgeThread();

        // Open/Close mines for the current window

        Logger.info("Processing mine window.");
        HourlyJobThread.processMineWindow();

        Logger.info("Processing Banes...");
        HourlyJobThread.processBanes();

        // Calculate bootstrap time and rest boot time to current time.

        Duration bootDuration = Duration.between(LocalDateTime.now(), bootTime);
        long bootSeconds = Math.abs(bootDuration.getSeconds());
        String boottime = String.format("%d hours %02d minutes %02d seconds", bootSeconds / 3600, (bootSeconds % 3600) / 60, (bootSeconds % 60));
        Logger.info("Bootstrap time was " + boottime);

        bootTime = LocalDateTime.now();

        Logger.info("Running garbage collection...");
        System.gc();
        return true;
    }

    protected boolean initDatabaselayer() {

        // Try starting a GOM <-> DB connection.
        try {

            Logger.info("Configuring GameObjectManager to use Database: '"
                    + ConfigManager.MB_DATABASE_NAME.getValue() + "' on "
                    + ConfigManager.MB_DATABASE_ADDRESS.getValue() + ':'
                    + ConfigManager.MB_DATABASE_PORT.getValue());

            DbManager.configureConnectionPool();

        } catch (Exception e) {
            Logger.error(e.getMessage());
            return false;
        }

        return true;
    }

    /**
     * Called to remove a client on "leave world", "quit game", killed client
     * process, etc.
     */

    public void removeClient(ClientConnection origin) {

        if (origin == null) {
            Logger.info(
                    "ClientConnection null in removeClient.");
            return;
        }

        PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(
                origin);

        if (playerCharacter == null)
            // TODO log this
            return;

        //cancel any trade
        if (playerCharacter.charItemManager != null)
            playerCharacter.charItemManager.endTrade(true);

        // Release any mine claims

        Mine.releaseMineClaims(playerCharacter);

        // logout
        long delta = MBServerStatics.LOGOUT_TIMER_MS;

        if (System.currentTimeMillis() - playerCharacter.getTimeStamp("LastCombatPlayer") < 60000) {
            delta = 60000;

        }
        playerCharacter.stopMovement(playerCharacter.getLoc());
        UpdateStateMsg updateStateMsg = new UpdateStateMsg();
        updateStateMsg.setPlayer(playerCharacter);

        updateStateMsg.setActivity(5);
        DispatchManager.dispatchMsgToInterestArea(playerCharacter, updateStateMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);

        if (playerCharacter.region != null)
            if (PlayerCharacter.CanBindToBuilding(playerCharacter, playerCharacter.region.parentBuildingID))
                playerCharacter.bindBuilding = playerCharacter.region.parentBuildingID;
            else
                playerCharacter.bindBuilding = 0;

        playerCharacter.getLoadedObjects().clear();
        playerCharacter.getLoadedStaticObjects().clear();

        LogoutCharacterJob logoutJob = new LogoutCharacterJob(playerCharacter, this);
        JobContainer jc = JobScheduler.getInstance().scheduleJob(logoutJob,
                System.currentTimeMillis() + delta);
        playerCharacter.getTimers().put("Logout", jc);
        playerCharacter.getTimestamps().put("logout", System.currentTimeMillis());

        //send update to friends that you are logged off.

        PlayerFriends.SendFriendsStatus(playerCharacter, false);

    }

    public void logoutCharacter(PlayerCharacter player) {

        if (player == null) {
            Logger.error("Unable to find PlayerCharacter to logout");
            return;
        }
        //remove player from loaded mobs agro maps
        for (AbstractWorldObject awo : WorldGrid.getObjectsInRangePartial(player.getLoc(), MBServerStatics.CHARACTER_LOAD_RANGE, MBServerStatics.MASK_MOB)) {
            Mob loadedMob = (Mob) awo;
            loadedMob.playerAgroMap.remove(player.getObjectUUID());
        }
        player.getTimestamps().put("logout", System.currentTimeMillis());
        player.setEnteredWorld(false);

        // remove from simulation and zero current loc

        WorldGrid.RemoveWorldObject(player);

        // clear Logout Timer

        if (player.getTimers() != null)
            player.getTimers().remove("Logout");

        if (player.getPet() != null)
            player.getPet().dismiss();

        NPCManager.dismissNecroPets(player);

        // Set player inactive so they quit loading for other players

        player.setActive(false);

        // Remove from group

        Group group = GroupManager.getGroup(player);

        try {
            if (group != null)
                GroupManager.LeaveGroup(player);
        } catch (Exception e) {
            Logger.error(e.toString());
        }

        player.respawnLock.writeLock().lock();
        try {
            if (!player.isAlive())
                player.respawn(false, false, true);
        } catch (Exception e) {
            Logger.error(e);
        } finally {
            player.respawnLock.writeLock().unlock();
        }
    }

    private void processTrashFile() {

        ArrayList<String> machineList;
        ArrayList<PlayerCharacter> trashList = new ArrayList<>();
        ArrayList<Integer> accountList = new ArrayList<>();

        File trashFile = new File("trash");

        if (trashFile.exists() == false)
            return;

        // Build list of machineID's in the trash file

        machineList = DbManager.AccountQueries.GET_TRASH_LIST();

        // Build list of trash characters associated with that machineID

        for (String machineID : machineList) {
            trashList = DbManager.AccountQueries.GET_ALL_CHARS_FOR_MACHINE(machineID);


            // Deactivate these players and add them to loginCache table

            for (PlayerCharacter trashPlayer : trashList) {

                if (trashPlayer == null)
                    continue;

                // Need to collate accounts.

                if (!accountList.contains(trashPlayer.getAccount().getObjectUUID()))
                    accountList.add(trashPlayer.getAccount().getObjectUUID());

                DbManager.PlayerCharacterQueries.SET_ACTIVE(trashPlayer, false);
                DbManager.AccountQueries.INVALIDATE_LOGIN_CACHE(trashPlayer.getObjectUUID(), "character");
            }
        }

        //  delete vault of associated accounts and then invalidate them
        //  in the login cache.

        for (Integer accountID : accountList) {
            DbManager.AccountQueries.DELETE_VAULT_FOR_ACCOUNT(accountID);
            DbManager.AccountQueries.INVALIDATE_LOGIN_CACHE(accountID, "account");
        }

        // Trigger the Login Server to invalidate these accounts in the cache..

        try {
            Files.write(Paths.get("cacheInvalid"), "".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }

        // If any of these players are active disconnect them.
        // The account and player should be removed from the login
        // server cache file by now.

        Timer timer = new Timer("Disconnect Trash");
        timer.schedule(new DisconnectTrashTask(trashList), 3000L);

        // Clean up after ourselves

        try {
            Files.deleteIfExists(Paths.get("trash"));
            DbManager.AccountQueries.CLEAR_TRASH_TABLE();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private void processFlashFile() {

        File flashFile = new File("flash");
        String flashString;
        List<String> fileContents;

        if (flashFile.exists() == false)
            return;

        try {
            fileContents = Files.readAllLines(Paths.get("flash"));
        } catch (IOException e) {
            return;
        }

        // Flash file detected: read contents
        // and send as a flash.

        flashString = fileContents.toString();

        if (flashString == null)
            return;

        if (flashString.equals(""))
            flashString = "Rebooting for to fix bug.";

        Logger.info("Sending flash from external interface");
        Logger.info("Msg: " + flashString);

        ChatSystemMsg msg = new ChatSystemMsg(null, flashString);
        msg.setChannel(mbEnums.ChatChannelType.FLASH.getChannelID());
        msg.setMessageType(mbEnums.ChatMessageType.INFO.ordinal());
        DispatchManager.dispatchMsgToAll(msg);

        // Delete file

        try {
            Files.deleteIfExists(Paths.get("flash"));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}