/*
 * This file is a part of the SteamWar software.
 *
 * Copyright (C) 2021  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.bausystem.features.util;

import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.SWUtils;
import de.steamwar.bausystem.features.world.WorldEditListener;
import de.steamwar.bausystem.shared.EnumDisplay;
import de.steamwar.command.PreviousArguments;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper;
import de.steamwar.inventory.SWAnvilInv;
import de.steamwar.inventory.SWInventory;
import de.steamwar.inventory.SWItem;
import de.steamwar.inventory.SWListInv;
import de.steamwar.linkage.Linked;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.Material;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.ItemStack;

import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Predicate;

@Linked
public class MaterialCommand extends SWCommand implements Listener {

    public MaterialCommand() {
        super("material", "baumaterial");
        WorldEditListener.addCommandExclusion("material");
    }

    private Map<Player, Search> searchMap = new HashMap<>();

    @Getter
    @Setter
    static class Search {
        SearchType transparent = SearchType.IGNORE;
        SearchType solid = SearchType.IGNORE;
        SearchType gravity = SearchType.IGNORE;
        SearchType occluding = SearchType.IGNORE;
        SearchType interacteable = SearchType.IGNORE;
        SearchType flammable = SearchType.IGNORE;
        SearchType burnable = SearchType.IGNORE;
        SearchType waterloggable = SearchType.IGNORE;
        SearchType unmoveable = SearchType.IGNORE;

        String blastResistance = ">=0";
        String name = "";
    }

    @AllArgsConstructor
    enum SearchType implements EnumDisplay {
        TRUE("MATERIAL_SEARCH_PROPERTY_TRUE", b -> b),
        FALSE("MATERIAL_SEARCH_PROPERTY_FALSE", b -> !b),
        IGNORE("MATERIAL_SEARCH_PROPERTY_IGNORE", b -> true);

        @Getter
        private String chatValue;
        private Predicate<Boolean> predicate;

        public boolean test(boolean b) {
            return predicate.test(b);
        }

        public SearchType next() {
            switch (this) {
                case TRUE:
                    return FALSE;
                case FALSE:
                    return IGNORE;
                case IGNORE:
                    return TRUE;
            }
            return IGNORE;
        }
    }

    @Register
    public void materialGUI(Player p) {
        materialGUI(p, searchMap.get(p));
    }

    private static final Map<String, BiConsumer<Search, SearchType>> elements = new HashMap<>();
    static {
        elements.put("-transparent", (search, searchType) -> search.transparent = searchType);
        elements.put("-solid", (search, searchType) -> search.solid = searchType);
        elements.put("-gravity", (search, searchType) -> search.gravity = searchType);
        elements.put("-occluding", (search, searchType) -> search.occluding = searchType);
        elements.put("-interacteable", (search, searchType) -> search.interacteable = searchType);
        elements.put("-flammable", (search, searchType) -> search.flammable = searchType);
        elements.put("-burnable", (search, searchType) -> search.burnable = searchType);
        elements.put("-waterloggable", (search, searchType) -> search.waterloggable = searchType);
        elements.put("-unmoveable", (search, searchType) -> search.unmoveable = searchType);
    }

    @Register
    public void search(Player p, @Mapper("search") String... searches) {
        Search search = new Search();
        for (String s : searches) {
            boolean has = false;
            for (Map.Entry<String, BiConsumer<Search, SearchType>> element: elements.entrySet()) {
                if (s.startsWith(element.getKey() + ":")) {
                    element.getValue().accept(search, SearchType.valueOf(s.substring(element.getKey().length() + 1).toUpperCase()));
                    has = true;
                } else if (s.startsWith(element.getKey().substring(0, 2) + ":")) {
                    element.getValue().accept(search, SearchType.valueOf(s.substring(element.getKey().substring(0, 2).length() + 1).toUpperCase()));
                    has = true;
                }
                if (has) break;
            }
            if (has) continue;
            switch (s.toLowerCase()) {
                case "-br:":
                case "-blastresistance:":
                    s = s.substring(s.indexOf(':') + 1).replace('_', ' ');
                    if (s.isEmpty() || s.matches("((([><]=?)|!|=)\\d+(\\.|,\\d+)?)( ((([><]=?)|!|=)\\d+(\\.|,\\d+)?))*")) {
                        search.blastResistance = s;
                    }
                    break;
                default:
                    search.name = s;
                    break;
            }
        }
        materialGUI(p, search);
    }

    @Mapper(value = "search", local = true)
    private TypeMapper<String> searchMapper() {
        List<String> results = new ArrayList<>();
        for (String s : Arrays.asList("-transparent", "-solid", "-gravity", "-occluding", "-interacteable", "-flammable", "-burnable", "-waterloggable")) {
            results.add(s + ":true");
            results.add(s + ":false");
            results.add(s + ":ignore");

            s = s.substring(0, 2);
            results.add(s + ":true");
            results.add(s + ":false");
            results.add(s + ":ignore");
        }

        return new TypeMapper<String>() {
            @Override
            public String map(CommandSender commandSender, PreviousArguments previousArguments, String s) {
                return s;
            }

            @Override
            public Collection<String> tabCompletes(CommandSender commandSender, PreviousArguments previousArguments, String s) {
                List<String> tabCompletes = new ArrayList<>();
                tabCompletes.addAll(results);
                tabCompletes.add(s);
                return tabCompletes;
            }
        };
    }

    public void materialGUI(Player p, Search search) {
        List<SWListInv.SWListEntry<Material>> swListEntries = new ArrayList<>();
        MaterialLazyInit.materialData.forEach(data -> {
            if (data.is(search)) {
                swListEntries.add(data.toSWItem(p));
            }
        });
        SWListInv<Material> materialSWListInv = new SWListInv<>(p, BauSystem.MESSAGE.parse("MATERIAL_INV_NAME", p, swListEntries.size(), MaterialLazyInit.materialData.size()), false, swListEntries, (clickType, material) -> {
            if (material.isItem()) {
                SWUtils.giveItemToPlayer(p, new ItemStack(material, 1));
            }
        });
        materialSWListInv.setItem(49, new SWItem(Material.NAME_TAG, BauSystem.MESSAGE.parse("MATERIAL_SEARCH", p), clickType -> {
            searchGUI(p);
        }));
        materialSWListInv.open();
    }

    private void searchGUI(Player p) {
        SWInventory swInventory = new SWInventory(p, 54, BauSystem.MESSAGE.parse("MATERIAL_SEARCH", p));
        Search search = searchMap.get(p);
        swInventory.setItem(45, new SWItem(Material.ARROW, BauSystem.MESSAGE.parse("MATERIAL_BACK", p), clickType -> {
            materialGUI(p);
        }));
        swInventory.setItem(10, new SWItem(Material.NAME_TAG, BauSystem.MESSAGE.parse("MATERIAL_SEARCH_NAME", p) + BauSystem.MESSAGE.parse("MATERIAL_SEARCH_VALUE", p, search.name), clickType -> {
            SWAnvilInv swAnvilInv = new SWAnvilInv(p, BauSystem.MESSAGE.parse("MATERIAL_SEARCH_NAME", p), search.name);
            swAnvilInv.setCallback(s -> {
                search.name = s;
                searchGUI(p);
            });
            swAnvilInv.open();
        }));
        swInventory.setItem(19, new SWItem(Material.GLASS, BauSystem.MESSAGE.parse("MATERIAL_SEARCH_TRANSPARENT", p) + BauSystem.MESSAGE.parse("MATERIAL_SEARCH_VALUE", p, BauSystem.MESSAGE.parse(search.transparent.getChatValue(), p)), clickType -> {
            search.transparent = search.transparent.next();
            searchGUI(p);
        }));
        swInventory.setItem(20, new SWItem(Material.BRICK, BauSystem.MESSAGE.parse("MATERIAL_SEARCH_SOLID", p) + BauSystem.MESSAGE.parse("MATERIAL_SEARCH_VALUE", p, BauSystem.MESSAGE.parse(search.solid.getChatValue(), p)), clickType -> {
            search.solid = search.solid.next();
            searchGUI(p);
        }));
        swInventory.setItem(21, new SWItem(Material.BLACK_CONCRETE_POWDER, BauSystem.MESSAGE.parse("MATERIAL_SEARCH_GRAVITY", p) + BauSystem.MESSAGE.parse("MATERIAL_SEARCH_VALUE", p, BauSystem.MESSAGE.parse(search.gravity.getChatValue(), p)), clickType -> {
            search.gravity = search.gravity.next();
            searchGUI(p);
        }));
        swInventory.setItem(22, new SWItem(Material.BIRCH_LOG, BauSystem.MESSAGE.parse("MATERIAL_SEARCH_OCCLUDING", p) + BauSystem.MESSAGE.parse("MATERIAL_SEARCH_VALUE", p, BauSystem.MESSAGE.parse(search.occluding.getChatValue(), p)), clickType -> {
            search.occluding = search.occluding.next();
            searchGUI(p);
        }));
        swInventory.setItem(23, new SWItem(Material.OAK_BUTTON, BauSystem.MESSAGE.parse("MATERIAL_SEARCH_INTERACTEABLE", p) + BauSystem.MESSAGE.parse("MATERIAL_SEARCH_VALUE", p, BauSystem.MESSAGE.parse(search.interacteable.getChatValue(), p)), clickType -> {
            search.interacteable = search.interacteable.next();
            searchGUI(p);
        }));
        swInventory.setItem(24, new SWItem(Material.FLINT_AND_STEEL, BauSystem.MESSAGE.parse("MATERIAL_SEARCH_FLAMMABLE", p) + BauSystem.MESSAGE.parse("MATERIAL_SEARCH_VALUE", p, BauSystem.MESSAGE.parse(search.flammable.getChatValue(), p)), clickType -> {
            search.flammable = search.flammable.next();
            searchGUI(p);
        }));
        swInventory.setItem(25, new SWItem(Material.LAVA_BUCKET, BauSystem.MESSAGE.parse("MATERIAL_SEARCH_BURNABLE", p) + BauSystem.MESSAGE.parse("MATERIAL_SEARCH_VALUE", p, BauSystem.MESSAGE.parse(search.burnable.getChatValue(), p)), clickType -> {
            search.burnable = search.burnable.next();
            searchGUI(p);
        }));
        swInventory.setItem(28, new SWItem(Material.WATER_BUCKET, BauSystem.MESSAGE.parse("MATERIAL_SEARCH_WATERLOGGABLE", p) + BauSystem.MESSAGE.parse("MATERIAL_SEARCH_VALUE", p, BauSystem.MESSAGE.parse(search.waterloggable.getChatValue(), p)), clickType -> {
            search.waterloggable = search.waterloggable.next();
            searchGUI(p);
        }));
        swInventory.setItem(29, new SWItem(Material.PISTON, BauSystem.MESSAGE.parse("MATERIAL_SEARCH_UNMOVEABLE", p) + BauSystem.MESSAGE.parse("MATERIAL_SEARCH_VALUE", p, BauSystem.MESSAGE.parse(search.unmoveable.getChatValue(), p)), clickType -> {
            search.unmoveable = search.unmoveable.next();
            searchGUI(p);
        }));
        swInventory.setItem(34, new SWItem(Material.NETHER_BRICK, BauSystem.MESSAGE.parse("MATERIAL_SEARCH_BLASTRESISTANCE", p) + BauSystem.MESSAGE.parse("MATERIAL_SEARCH_VALUE", p, search.blastResistance), clickType -> {
            SWAnvilInv swAnvilInv = new SWAnvilInv(p, BauSystem.MESSAGE.parse("MATERIAL_SEARCH_BLASTRESISTANCE", p), search.blastResistance);
            swAnvilInv.setCallback(s -> {
                if (s.isEmpty() || s.matches("((([><]=?)|!|=)\\d+(\\.|,\\d+)?)( ((([><]=?)|!|=)\\d+(\\.|,\\d+)?))*")) {
                    search.blastResistance = s;
                }
                searchGUI(p);
            });
            swAnvilInv.open();
        }));
        swInventory.open();
    }

    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent event) {
        searchMap.put(event.getPlayer(), new Search());
    }

    @EventHandler
    public void onPlayerQuit(PlayerQuitEvent event) {
        searchMap.remove(event.getPlayer());
    }
}
