/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2026 SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar.sql;

import lombok.ToString;

import java.io.File;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@ToString
public final class GameModeConfig<M, W> {

    public static final Function<String, String> ToString = Function.identity();
    public static final Function<File, String> ToStaticWarGear = GameModeConfig::constWarGear;
    public static final Function<File, String> ToInternalName = GameModeConfig::internalName;
    public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy HH:mm");
    private static final Random random = new Random();

    private static String constWarGear(File f) {
        return "WarGear";
    }

    private static String internalName(File f) {
        return f != null ? f.getName().replace(".yml", "") : constWarGear(null);
    }

    private static final Map<String, GameModeConfig<?, String>> byFileName;
    private static final Map<String, GameModeConfig<?, String>> byGameName;
    private static final Map<SchematicType, GameModeConfig<?, String>> bySchematicType;

    public static <M> Collection<GameModeConfig<M, String>> getAll() {
        return (Collection) byFileName.values();
    }

    public static <M> GameModeConfig<M, String> getByFileName(File file) {
        return (GameModeConfig<M, String>) byFileName.get(file.getName());
    }

    public static <M> GameModeConfig<M, String> getByGameName(String gameName) {
        return (GameModeConfig<M, String>) byGameName.get(gameName);
    }

    public static <M> GameModeConfig<M, String> getBySchematicType(SchematicType schematicType) {
        return (GameModeConfig<M, String>) bySchematicType.get(schematicType);
    }

    private static final GameModeConfig<?, String> DEFAULTS;

    public static <M> GameModeConfig<M, String> getDefaults() {
        return (GameModeConfig<M, String>) DEFAULTS;
    }

    private static final Field Schematic_TypeField;
    private static final Field Schematic_SubTypesField;

    static {
        try {
            Schematic_TypeField = SchematicConfig.class.getDeclaredField("Type");
            Schematic_TypeField.setAccessible(true);

            Schematic_SubTypesField = SchematicConfig.class.getDeclaredField("SubTypes");
            Schematic_SubTypesField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            throw new SecurityException(e.getMessage(), e);
        }

        byFileName = new HashMap<>();
        byGameName = new HashMap<>();
        bySchematicType = new HashMap<>();
        DEFAULTS = SQLWrapper.impl.loadGameModeConfig(null);
        init();
    }

    public static void init() {
        byFileName.clear();
        byGameName.clear();
        bySchematicType.clear();

        File folder = SQLWrapper.impl.getSchemTypesFolder();
        if (!folder.exists()) return;
        if (!folder.isDirectory()) return;

        for (File file : Objects.requireNonNull(folder.listFiles())) {
            if (!file.getName().endsWith(".yml")) continue;
            if (file.getName().endsWith(".kits.yml")) continue;
            SQLWrapper.impl.loadGameModeConfig(file);
        }

        byFileName.values().forEach(gameModeConfig -> {
            List<SchematicType> subTypes = Collections.unmodifiableList(gameModeConfig.Schematic.SubTypesStrings.stream()
                    .map(SchematicType::fromDB)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList()));
            try {
                Schematic_SubTypesField.set(gameModeConfig.Schematic, subTypes);
            } catch (IllegalAccessException e) {
                throw new SecurityException(e.getMessage(), e);
            }
        });
    }

    public final boolean loaded;
    public final File configFile;

    /**
     * Bundle for server specific configuration values
     */
    public final ServerConfig Server;

    /**
     * Submission deadline for schematics in 'dd.MM.yyyy HH:mm' format
     *
     * @implSpec {@code null} by default
     */
    public final Date Deadline;

    /**
     * The questions that have to be answered to accept the schematic
     *
     * @implSpec Disables check schem type if missing
     */
    public final List<String> CheckQuestions;

    /**
     * The allowed checkers to check this schematic type denoted by a list of SteamWar ids.
     * The people need the {@link UserPerm#CHECK} to be able to check though.
     *
     * @implSpec {@code []} by default -> denoting every person with {@link UserPerm#CHECK} can check it
     */
    public final List<Integer> Checkers;

    /**
     * Bundle for countdowns during the fight
     */
    public final TimesConfig Times;

    /**
     * Bundle for arena specific configurations. Primarily used by the FightSystem
     */
    public final ArenaConfig Arena;

    /**
     * Bundle for schematic configurations
     */
    public final SchematicConfig<M> Schematic;

    /**
     * The name of the game mode presented to the players
     *
     * @implSpec {@code YMLWrapper.getDefaultGameName()} by default
     */
    public final String GameName;

    /**
     * The months this game mode should be active and playable<br/>
     * The empty List means all of them
     *
     * @implNote 1 is January - 12 is December
     */
    public final List<Integer> ActiveMonths;

    /**
     * The prefix used for team chats
     *
     * @implSpec {@code +} by default
     */
    public final String TeamChatPrefix;

    /**
     * Bundle for configurations for the Blue team
     */
    public final BlueConfig Blue;

    /**
     * Bundle for configurations for the Red team
     */
    public final RedConfig Red;

    /**
     * The list of active win conditions
     */
    public final List<W> WinConditions;

    /**
     * Bundle for configurations used by the Winconditions in the FightSystem
     */
    public final WinConditionParamsConfig<M> WinConditionParams;

    /**
     * Bundle for Kit specific configurations
     */
    public final KitsConfig<M> Kits;

    /**
     * A list of integers containing the waiting time of this enter stage in the fight
     */
    public final List<Integer> EnterStages;

    /**
     * Bundle for Techhider configurations
     */
    public final TechhiderConfig<M> Techhider;

    public GameModeConfig(File file, Function<String, M> materialMapper, Function<String, W> winconditionMapper, Function<File, String> defaultGameName, boolean cacheInstance) {
        YMLWrapper<M, W> loader = new YMLWrapper<>(file, materialMapper, winconditionMapper);

        configFile = file;
        loaded = loader.canLoad();
        Server = new ServerConfig(loader.with("Server"));

        String deadlineString = loader.getString("Deadline", null);
        if (deadlineString != null) {
            Date Deadline = null;
            try {
                Deadline = DATE_FORMAT.parse(deadlineString);
            } catch (ParseException e) {
                Deadline = null;
            }
            this.Deadline = Deadline;
        } else {
            Deadline = null;
        }

        CheckQuestions = loader.getStringList("CheckQuestions");
        Checkers = loader.getIntList("Checkers");
        Times = new TimesConfig(loader.with("Times"));
        // Arena would be here to be in config order but needs Schematic.Size and EnterStages loaded afterwards
        Schematic = new SchematicConfig<>(loader.with("Schematic"));
        GameName = loader.getString("GameName", defaultGameName.apply(file));
        ActiveMonths = loader.getIntList("ActiveMonths");
        TeamChatPrefix = loader.getString("TeamChatPrefix", "+");
        Blue = new BlueConfig(loader.with("Blue"));
        Red = new RedConfig(loader.with("Red"));
        WinConditions = Collections.unmodifiableList(loader.getStringList("WinConditions").stream().map(loader.winconditionMapper).collect(Collectors.toList()));
        WinConditionParams = new WinConditionParamsConfig<>(loader.with("WinConditionParams"));
        Kits = new KitsConfig<>(loader.with("Kits"));
        EnterStages = loader.getIntList("EnterStages");
        Techhider = new TechhiderConfig<>(loader.with("Techhider"));

        Arena = new ArenaConfig(loader.with("Arena"), Schematic.Size, EnterStages);

        if (cacheInstance && file != null) {
            byFileName.put(configFile.getName(), (GameModeConfig) this);
            byGameName.put(GameName, (GameModeConfig) this);
        }

        if (Schematic.Type != null) {
            if (cacheInstance && file != null) {
                bySchematicType.put(Schematic.Type, (GameModeConfig) this);
                if (Schematic.Type.checkType() != null) {
                    bySchematicType.put(Schematic.Type.checkType(), (GameModeConfig) this);
                }
            }
            return;
        }
        if (!Schematic.loaded) return;
        String Schematic_Type = loader.with("Schematic").getString("Type", "Normal");
        if (Schematic_Type.equals("Normal")) return;
        String Schematic_Shortcut = Schematic.Shortcut;

        SchematicType checktype = null;
        String material = loader.with("Schematic").getString("Material", "STONE_BUTTON");

        if (!CheckQuestions.isEmpty()) {
            checktype = new SchematicType("C" + Schematic_Type, "C" + Schematic_Shortcut, SchematicType.Type.CHECK_TYPE, null, material, true);
        }
        SchematicType current = new SchematicType(Schematic_Type, Schematic_Shortcut, Server.loaded ? SchematicType.Type.FIGHT_TYPE : SchematicType.Type.NORMAL, checktype, material, Deadline, Schematic.ManualCheck);
        try {
            Schematic_TypeField.set(Schematic, current);

            if (cacheInstance && file != null) {
                bySchematicType.put(Schematic.Type, (GameModeConfig) this);
                if (Schematic.Type.checkType() != null) {
                    bySchematicType.put(Schematic.Type.checkType(), (GameModeConfig) this);
                }
            }
        } catch (IllegalAccessException e) {
            throw new SecurityException(e.getMessage(), e);
        }
    }

    @ToString
    public static final class ServerConfig {

        public final boolean loaded;

        /**
         * Base server folder
         */
        public final String Folder;

        /**
         * Server java archive
         */
        public final String ServerJar;

        /**
         * Available arenas
         */
        public final List<String> Maps;

        /**
         * Names to address the game mode in the chat interface
         */
        public final List<String> ChatNames;

        /**
         * If the Server is a Spigot server
         *
         * @implSpec {@code false} by default
         */
        public final boolean Spigot;

        /**
         * If the game mode should be marked as a historic game mode
         *
         * @implSpec {@code false} by default
         */
        public final boolean Historic;

        /**
         * If ranked matches should be available for the game mode
         *
         * @implSpec {@code false} by default
         */
        public final boolean Ranked;

        private ServerConfig(YMLWrapper loader) {
            loaded = loader.canLoad();
            Folder = loader.getString("Folder", null);
            ServerJar = loader.getString("ServerJar", null);
            Maps = loader.getStringList("Maps");
            ChatNames = loader.getStringList("ChatNames");
            Spigot = loader.getBoolean("Spigot", false);
            Historic = loader.getBoolean("Historic", false);
            Ranked = loader.getBoolean("Ranked", false);
        }
    }

    @ToString
    public static final class TimesConfig {
        public final boolean loaded;

        /**
         * Time in seconds the server stops after starting if nobody joins
         *
         * @implSpec {@code 300} by default
         */
        public final int NoPlayersOnlineDuration;

        /**
         * Time in seconds the team leaders have to choose their schematic
         *
         * @implSpec {@code 120} by default
         */
        public final int PreSchemPasteDuration;

        /**
         * Time in seconds for preparing
         *
         * @implSpec {@code 300} by default
         */
        public final int SetupDuration;

        /**
         * Time in seconds the final countdown is long
         *
         * @implSpec {@code 30} by default
         */
        public final int PreFightDuration;

        /**
         * Time in seconds to spectate the arena after the fight
         *
         * @implSpec {@code 30} by default
         */
        public final int SpectatorDuration;

        private TimesConfig(YMLWrapper loader) {
            loaded = loader.canLoad();
            NoPlayersOnlineDuration = loader.getInt("NoPlayersOnlineDuration", 300);
            PreSchemPasteDuration = loader.getInt("PreSchemPasteDuration", 120);
            SetupDuration = loader.getInt("SetupDuration", 300);
            PreFightDuration = loader.getInt("PreFightDuration", 30);
            SpectatorDuration = loader.getInt("SpectatorDuration", 60);
        }
    }

    @ToString
    public static final class ArenaConfig {

        public final boolean loaded;

        /**
         * The amount of blocks the schematics should be pasted under the surface
         *
         * @implSpec {@code 0} by default
         */
        public final int WaterDepth;

        /**
         * If TNT should break blocks even underwater
         *
         * @implSpec {@code true} by default
         */
        public final boolean WaterDamage;

        /**
         * The outer border of the arena, measured in blocks around the schematic areas
         */
        public final Schem2BorderConfig Schem2Border;

        /**
         * The offset the teams spawn relative to the center of their area
         */
        public final SpawnOffsetConfig SpawnOffset;

        /**
         * The size of the team areas are expanded around the schematics
         *
         * @implSpec {@code 12} by default
         */
        public final int BorderFromSchematic;

        /**
         * If ground walkable, teams can walk below the lower arena border during setup
         *
         * @implSpec {@code true} by default
         */
        public final boolean GroundWalkable;

        /**
         * Disable snow and ice melting
         *
         * @implSpec {@code false} by default
         */
        public final boolean DisableSnowMelt;

        /**
         * Disable ice forming
         *
         * @implSpec {@code false} by default
         */
        public final boolean DisableIceForm;

        /**
         * Allow leaving the arena area as spectator
         *
         * @implSpec {@code false} by default
         */
        public final boolean Leaveable;

        /**
         * Allow missiles to fly to the enemy and not stop at the schem border.
         *
         * @implSpec {@code !EnterStages.isEmpty()} by default
         */
        public final boolean AllowMissiles;

        /**
         * Denotes that there is no floor for this GameMode
         *
         * @implSpec {@code false} by default
         */
        public final boolean NoFloor;

        private ArenaConfig(YMLWrapper loader, SchematicConfig.SizeConfig Size, List<Integer> EnterStages) {
            loaded = loader.canLoad();
            WaterDepth = loader.getInt("WaterDepth", 0);
            WaterDamage = loader.getBoolean("WaterDamage", true);
            Schem2Border = new Schem2BorderConfig(loader.with("Schem2Border"));
            SpawnOffset = new SpawnOffsetConfig(loader.with("SpawnOffset"), Size);
            BorderFromSchematic = loader.getInt("BorderFromSchematic", 21);
            GroundWalkable = loader.getBoolean("GroundWalkable", true);
            DisableSnowMelt = loader.getBoolean("DisableSnowMelt", false);
            DisableIceForm = loader.getBoolean("DisableIceForm", false);
            Leaveable = loader.getBoolean("Leaveable", false);
            AllowMissiles = loader.getBoolean("AllowMissiles", !EnterStages.isEmpty());
            NoFloor = loader.getBoolean("NoFloor", false);
        }

        @ToString
        public static final class Schem2BorderConfig {

            public final boolean loaded;

            /**
             * @implSpec {@code 24} by default
             */
            public final int x;

            /**
             * @implSpec {@code 24} by default
             */
            public final int z;

            private Schem2BorderConfig(YMLWrapper loader) {
                loaded = loader.canLoad();
                x = loader.getInt("x", 24);
                z = loader.getInt("z", 24);
            }
        }

        @ToString
        public static final class SpawnOffsetConfig {

            public final boolean loaded;

            /**
             * @implSpec {@code 0} by default
             */
            public final double x;

            /**
             * @implSpec {@code Schematic.Size.y} by default
             */
            public final double y;

            /**
             * @implSpec {@code 0} by default
             */
            public final double z;

            private SpawnOffsetConfig(YMLWrapper loader, SchematicConfig.SizeConfig Size) {
                loaded = loader.canLoad();
                x = loader.getDouble("x", 0);
                y = loader.getDouble("y", Size.y);
                z = loader.getDouble("z", 0);
            }
        }
    }

    @ToString
    public static final class SchematicConfig<M> {

        public final boolean loaded;

        /**
         * The size of the schematics
         */
        public final SizeConfig Size;

        /**
         * Used for GameModes with a technic area
         */
        public final InsetConfig Inset;

        /**
         * The schematic type that can be chosen in this arena
         *
         * @implSpec {@code Normal} by default
         */
        public final SchematicType Type;

        private final List<String> SubTypesStrings;

        /**
         * The schematic types that are also allowed to be chosen in this arena
         */
        public final List<SchematicType> SubTypes;

        /**
         * Shortcut of the schematic type
         *
         * @implSpec {@code ""} by default
         */
        public final String Shortcut;

        /**
         * Spigot (1.8) material for GUIs
         *
         * @implSpec {@code STONE_BUTTON} by default
         */
        public final M Material;

        /**
         * Manual check of schematic necessary
         *
         * @implSpec {@code true} by default
         */
        public final boolean ManualCheck;

        /**
         * If the schematics should be rotated during pasting
         *
         * @implSpec {@code true} by default
         */
        public final boolean Rotate;

        /**
         * If the schematics should be pasted aligned to the borders instead of centered
         *
         * @implSpec {@code false} by default
         */
        public final boolean PasteAligned;

        /**
         * If only public schematics are allowed
         *
         * @implSpec {@code false} by default
         */
        public final boolean OnlyPublicSchematics;

        /**
         * If the public only force should be completely disabled
         *
         * @implSpec {@code false} by default
         */
        public final boolean IgnorePublicOnly;

        /**
         * Replacements that should be done during PRE_RUNNING with no block updates
         *
         * @implSpec {@code {}} by default
         */
        public final Map<M, M> ReplacementsWithoutBlockUpdates;

        /**
         * Replacements that should be done during PRE_RUNNING with block updates
         *
         * @implSpec {@code {}} by default
         */
        public final Map<M, M> ReplacementsWithBlockUpdates;

        /**
         * If the schematic perparation arena mode is time limited
         *
         * @implSpec {@code false} by default
         */
        public final boolean UnlimitedPrepare;

        /**
         * Maximal amount of blocks allowed in the schematic
         *
         * @implSpec {@code 0} by default
         */
        public final int MaxBlocks;

        /**
         * Maximal amount of items per dispenser
         *
         * @implSpec {@code 128} by default
         */
        public final int MaxDispenserItems;

        /**
         * Maximal blast resistance for the blocks
         *
         * @implSpec {@code Double.MAX_VALUE} by default
         */
        public final double MaxBlastResistance;

        /**
         * Maximal blast resistance for the design blocks
         *
         * @implSpec {@link SchematicConfig#MaxBlastResistance} by default
         */
        public final double MaxDesignBlastResistance;

        /**
         * List of limited material (combinations)<br/>
         * List contains tags Amount (integer) and Materials (List of material names in Spigot 1.12 AND Spigot 1.15 format)
         */
        public final Map<Set<M>, Integer> Limited;

        private SchematicConfig(YMLWrapper<M, ?> loader) {
            loaded = loader.canLoad();
            Size = new SizeConfig(loader.with("Size"));
            Inset = new InsetConfig(loader.with("Inset"));
            Type = null;
            SubTypesStrings = loader.getStringList("SubTypes");
            SubTypes = new ArrayList<>();
            Shortcut = loader.getString("Shortcut", "");
            Material = loader.getMaterial("Material", "STONE_BUTTON");
            ManualCheck = loader.getBoolean("ManualCheck", true);
            Rotate = loader.getBoolean("Rotate", true);
            PasteAligned = loader.getBoolean("PasteAligned", false);
            OnlyPublicSchematics = loader.getBoolean("OnlyPublicSchematics", false);
            IgnorePublicOnly = loader.getBoolean("IgnorePublicOnly", false);
            UnlimitedPrepare = loader.getBoolean("UnlimitedPrepare", false);
            MaxBlocks = loader.getInt("MaxBlocks", 0);
            MaxDispenserItems = loader.getInt("MaxDispenserItems", 128);
            MaxBlastResistance = loader.getDouble("MaxBlastResistance", Double.MAX_VALUE);
            MaxDesignBlastResistance = loader.getDouble("MaxDesignBlastResistance", MaxBlastResistance);

            Map<Set<M>, Integer> Limited = new HashMap<>();
            for (Map<?, ?> entry : loader.getMapList("Limited")) {
                int amount = (Integer) entry.get("Amount");
                Set<String> materials = new HashSet<>((List<String>) entry.get("Materials"));
                if (amount == 0) {
                    materials.forEach(material -> {
                        Limited.put(Collections.singleton(loader.materialMapper.apply(material.toUpperCase())), 0);
                    });
                } else {
                    Limited.put(Collections.unmodifiableSet(materials.stream().map(String::toUpperCase).map(loader.materialMapper).collect(Collectors.toSet())), amount);
                }
            }
            SQLWrapper.impl.getMaterialWithGreaterBlastResistance(MaxBlastResistance).forEach(material -> {
                if (Limited.entrySet().stream().anyMatch(entry -> entry.getKey().contains(material))) return;
                Limited.put(Collections.singleton((M) material), 0);
            });
            this.Limited = Collections.unmodifiableMap(Limited);

            this.ReplacementsWithoutBlockUpdates = loader.getMap("ReplacementsWithoutBlockUpdates", loader.materialMapper, loader.materialMapper);
            this.ReplacementsWithBlockUpdates = loader.getMap("ReplacementsWithBlockUpdates", loader.materialMapper, loader.materialMapper);
        }

        @ToString
        public static final class SizeConfig {

            public final boolean loaded;

            /**
             * @implSpec {@code 0} by default
             */
            public final int x;

            /**
             * @implSpec {@code 0} by default
             */
            public final int y;

            /**
             * @implSpec {@code 0} by default
             */
            public final int z;

            private SizeConfig(YMLWrapper loader) {
                loaded = loader.canLoad();
                x = loader.getInt("x", 0);
                y = loader.getInt("y", 0);
                z = loader.getInt("z", 0);
            }
        }

        @ToString
        public static final class InsetConfig {

            public final boolean loaded;

            /**
             * @implSpec {@code 0} by default
             */
            public final int x;

            /**
             * @implSpec {@code 0} by default
             */
            public final int z;

            /**
             * @implSpec {@code 0} by default
             */
            public final int top;

            /**
             * @implSpec {@code 0} by default
             */
            public final int bottom;

            private InsetConfig(YMLWrapper loader) {
                loaded = loader.canLoad();
                x = loader.getInt("x", 0);
                z = loader.getInt("z", 0);
                top = loader.getInt("top", 0);
                bottom = loader.getInt("bottom", 0);
            }
        }
    }

    @ToString
    public static final class BlueConfig {

        public final boolean loaded;

        /**
         * @implSpec {@code Blau} by default
         */
        public final String Name;

        /**
         * @implSpec {@code §9} by default
         */
        public final String Prefix;

        private BlueConfig(YMLWrapper loader) {
            loaded = loader.canLoad();
            Name = loader.getString("Name", "Blau");
            Prefix = loader.getString("Prefix", "§9");
        }
    }

    @ToString
    public static final class RedConfig {

        public final boolean loaded;

        /**
         * @implSpec {@code Rot} by default
         */
        public final String Name;

        /**
         * @implSpec {@code §c} by default
         */
        public final String Prefix;

        private RedConfig(YMLWrapper loader) {
            loaded = loader.canLoad();
            Name = loader.getString("Name", "Rot");
            Prefix = loader.getString("Prefix", "§c");
        }
    }

    @ToString
    public static final class WinConditionParamsConfig<M> {

        public final boolean loaded;

        /**
         * The time of any of the timeout win conditions in seconds
         *
         * @implSpec {@code 1200} by default
         */
        public final int TimeoutTime;

        /**
         * The percentage when any of the percent win conditions limits or triggers a win
         *
         * @implSpec {@code 7.0} by default
         */
        public final double PercentWin;

        /**
         * Does the percentage still change after the start of the enter phase
         *
         * @implSpec {@code true} by default
         */
        public final boolean PercentEntern;

        /**
         * Is Blocks a whitelist (true) or blacklist (false)
         *
         * @implSpec {@code false} by default
         */
        public final boolean BlocksWhitelist;

        /**
         * Special Blocks (Valid spigot material values) used by the percent win conditions
         */
        public final List<M> Blocks;

        /**
         * Time for being declared TechKo without a shot given.
         *
         * @implSpec {@code 90} by default
         */
        public final int TechKoTime;

        private WinConditionParamsConfig(YMLWrapper<M, ?> loader) {
            loaded = loader.canLoad();
            TimeoutTime = loader.getInt("TimeoutTime", 1200);
            PercentWin = loader.getDouble("PercentWin", 7.0);
            PercentEntern = loader.getBoolean("PercentEntern", true);
            BlocksWhitelist = loader.getBoolean("BlocksWhitelist", false);
            Blocks = loader.getMaterialList("Blocks");
            TechKoTime = loader.getInt("TechKoTime", 90);
        }
    }

    @ToString
    public static final class KitsConfig<M> {

        public final boolean loaded;

        /**
         * The kit file for this configuration
         *
         * @implSpec {@code kits.yml} by default
         */
        public final String File;

        /**
         * The default kit for team members
         *
         * @implSpec {@code default} by default
         */
        public final String MemberDefault;

        /**
         * The default kit for team leaders
         *
         * @implSpec {@code default} by default
         */
        public final String LeaderDefault;

        /**
         * If the personal kit system is active
         *
         * @implSpec {@code false} by default
         */
        public final boolean PersonalKits;

        /**
         * Items (Valid spigot material values) that are not allowed in the personal kit
         */
        public final List<M> ForbiddenItems;

        private KitsConfig(YMLWrapper<M, ?> loader) {
            loaded = loader.canLoad();
            File = loader.getString("File", "kits.yml");
            MemberDefault = loader.getString("MemberDefault", "default");
            LeaderDefault = loader.getString("LeaderDefault", "default");
            PersonalKits = loader.getBoolean("PersonalKits", false);
            ForbiddenItems = loader.getMaterialList("ForbiddenItems");
        }
    }

    @ToString
    public static final class TechhiderConfig<M> {

        public final boolean loaded;

        /**
         * Activates the tech hider
         *
         * @implSpec {@code false} by default
         */
        public final boolean Active;

        /**
         * Which block the tech hider replaces to.
         *
         * @implSpec {@code end_stone} by default
         */
        public final M ObfuscateWith;

        /**
         * A list of all hidden blocks. "water" results in the hiding of all waterlogged blocks as well.
         */
        public final Set<M> HiddenBlocks;

        /**
         * The block entity contents that are hidden (here with minecraft:nametag)
         */
        public final Set<String> HiddenBlockEntities;

        private TechhiderConfig(YMLWrapper<M, ?> loader) {
            loaded = loader.canLoad();
            Active = loader.getBoolean("Active", false);
            ObfuscateWith = loader.getMaterial("ObfuscateWith", "end_stone");
            HiddenBlocks = Collections.unmodifiableSet(new HashSet<>(loader.getMaterialList("HiddenBlocks")));
            HiddenBlockEntities = Collections.unmodifiableSet(new HashSet<>(loader.getStringList("HiddenBlockEntities")));
        }
    }

    public boolean isAfterDeadline() {
        return Deadline != null && Deadline.before(Date.from(Instant.now()));
    }

    public String hasMap(String map) {
        for (String m : Server.Maps) {
            if (m.equalsIgnoreCase(map)) return m;
        }
        return null;
    }

    public String getRandomMap() {
        return Server.Maps.get(random.nextInt(Server.Maps.size()));
    }

    public String convertToRealMapName(String map) {
        for (String m : Server.Maps) {
            if (m.equalsIgnoreCase(map)) return m;
        }
        return null;
    }

    public String getChatName() {
        return Server.ChatNames.get(0);
    }

    public boolean isActive() {
        if (Server.ChatNames.isEmpty()) return false;
        if (ActiveMonths.isEmpty()) return true;
        return ActiveMonths.contains(LocalDateTime.now().getMonth().getValue());
    }

    public String getSchemTypeOrInternalName() {
        if (Schematic.loaded) {
            return Schematic.Type.toDB();
        }
        return configFile.getName().replace(".yml", "");
    }
}
