/*
   This file is a part of the SteamWar software.

   Copyright (C) 2023  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.schematicsystem.autocheck;

import de.steamwar.core.Core;
import de.steamwar.schematicsystem.CheckSchemType;
import de.steamwar.schematicsystem.SchematicSystem;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
import net.md_5.bungee.api.chat.ClickEvent;
import org.bukkit.Material;
import org.bukkit.entity.Player;

import java.util.*;

@Builder
@Getter
@ToString
public class AutoCheckerResult {
    private final CheckSchemType type;
    private final int width;
    private final int height;
    private final int depth;
    private final AutoChecker.BlockScanResult blockScanResult;
    private final List<BlockPos> entities;

    public boolean isOk() {
        return blockScanResult.getRecords().isEmpty() &&
                blockScanResult.getForbiddenItems().isEmpty() &&
                blockScanResult.getForbiddenNbt().isEmpty() &&
                isSizeOk() &&
                isBlockCountOk() &&
                isLimitedBlocksOK() &&
                isDispenserItemsOK() &&
                !type.isAfterDeadline() &&
                entities.isEmpty() &&
                isDesignBlastResistanceOK();
    }

    public boolean fastOk() {
        return isSizeOk() &&
                !type.isAfterDeadline();
    }

    public boolean isDispenserItemsOK() {
        return blockScanResult.getDispenserItems().values().stream().allMatch(i -> i <= type.getMaxDispenserItems());
    }

    public boolean hasWarnings() {
        return blockScanResult.getDefunctNbt().isEmpty();
    }

    public boolean isSizeOk() {
        return !isTooWide() && !isTooHigh() && !isTooDeep();
    }

    public boolean isTooWide() {
        return width > type.getWidth();
    }

    public boolean isTooHigh() {
        return height > type.getHeight();
    }

    public boolean isTooDeep() {
        return depth > type.getDepth();
    }

    public boolean isBlockCountOk() {
        return type.getMaxBlocks() == 0 || blockScanResult.getBlockCounts().entrySet().stream().filter(entry -> entry.getKey() != Material.AIR).map(Map.Entry::getValue).reduce(Integer::sum).map(i -> i <= type.getMaxBlocks()).orElse(false);
    }

    public boolean isLimitedBlocksOK() {
        try {
            return type.getLimits().entrySet().stream()
                    .map(setIntegerEntry -> setIntegerEntry.getKey().stream().map(Material::getMaterial).map(blockScanResult.getBlockCounts()::get).map(i -> i == null || i <= setIntegerEntry.getValue()).reduce(Boolean::logicalAnd).orElse(false))
                    .reduce(Boolean::logicalAnd).orElse(true);
        } catch (NullPointerException e) {
            return false;
        }
    }

    public boolean isDesignBlastResistanceOK() {
        return blockScanResult.getDesignBlocks().keySet().stream().map(Material::getBlastResistance).noneMatch(i -> i > type.getMaxBlastResistance());
    }

    public void sendErrorMessage(Player p, String schemName) {
        SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_HEADER", p, schemName);
        if(isTooWide()) SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_WIDTH", p, width, type.getWidth());
        if(isTooHigh()) SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_HEIGHT", p, height, type.getHeight());
        if(isTooDeep()) SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_LENGTH", p, depth, type.getDepth());
        if(type.getMaxBlocks() != 0 && !isBlockCountOk()) {
            SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_BLOCKS", p, blockScanResult.getBlockCounts().values().stream().reduce(Integer::sum).orElse(0), type.getMaxBlocks());
        }
        if(!isLimitedBlocksOK()) {
            type.getLimits().forEach((strings, integer) -> {
                for (String string : strings) {
                    Material mat = Material.matchMaterial(string);
                    if(mat != null && blockScanResult.getBlockCounts().getOrDefault(mat, 0) > integer) {
                        if(integer == 0) {
                            SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_FORBIDDEN_BLOCK", p, mat.name());
                        } else {
                            SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_TOO_MANY_BLOCK", p, mat.name(), blockScanResult.getBlockCounts().getOrDefault(mat, 0), integer);
                        }
                    }
                }
            });
        }
        blockScanResult.getDispenserItems().entrySet().stream().filter(blockVector3IntegerEntry -> blockVector3IntegerEntry.getValue() > type.getMaxDispenserItems()).forEach(blockVector3IntegerEntry -> {
            SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_TOO_MANY_DISPENSER_ITEMS", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(blockVector3IntegerEntry.getKey()),
                    blockVector3IntegerEntry.getKey().getBlockX(),
                    blockVector3IntegerEntry.getKey().getBlockY(),
                    blockVector3IntegerEntry.getKey().getBlockZ(),
                    blockVector3IntegerEntry.getValue(),
                    type.getMaxDispenserItems());
        });
        blockScanResult.getRecords().forEach(blockVector3 -> {
            SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_RECORD", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(blockVector3), blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ());
        });
        blockScanResult.getForbiddenItems().forEach((blockVector3, materials) -> {
            SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_FORBIDDEN_ITEM", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(blockVector3), blockVector3.getX(), blockVector3.getY(), blockVector3.getZ(), setToString(materials));
        });
        blockScanResult.getForbiddenNbt().forEach((blockVector3, materials) -> {
            SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_FORBIDDEN_ITEM_NBT", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(blockVector3), blockVector3.getX(), blockVector3.getY(), blockVector3.getZ(), setToString(materials));
        });
        blockScanResult.getDefunctNbt().forEach(blockVector3 -> {
            SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_DEFUNCT_NBT", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(blockVector3), blockVector3.getX(), blockVector3.getY(), blockVector3.getZ());
        });
        if(Core.getVersion() > 12) {
            blockScanResult.getDesignBlocks().forEach((material, poss) -> {
                if(material.getBlastResistance() > type.getMaxBlastResistance()) {
                    poss.forEach(pos -> {
                        SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_DESIGN_BLOCK", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(pos), material.name(), pos.getBlockX(), pos.getBlockY(), pos.getBlockZ());
                    });
                }
            });
        }
        entities.forEach(blockPos -> {
            SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_ENTITY", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(blockPos), blockPos.getX(), blockPos.getY(), blockPos.getZ());
        });
        if(type.isAfterDeadline()) {
            SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_AFTER_DEADLINE", p, type.getDeadline());
        }
    }

    private static ClickEvent tpCommandTo(BlockPos pos) {
        return new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/tp " + pos.getBlockX() + " " + pos.getBlockY() + " " + pos.getBlockZ());
    }

    private static String setToString(Collection<Material> set) {
        return set.stream().map(material -> material.getKey().getKey()).reduce((s, s2) -> s + "§7, §c" + s2).orElse("");
    }
}
