/*
 * 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.slaves.panzern;

import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockTypes;
import de.steamwar.bausystem.utils.WorldEditUtils;
import lombok.Getter;
import lombok.SneakyThrows;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;

import java.util.*;

public class Panzern {

    private static List<PanzernAlgorithm> panzernAlgorithmList = new ArrayList<>();
    public static void add(PanzernAlgorithm panzernAlgorithm) {
        panzernAlgorithmList.add(panzernAlgorithm);
    }

    private static final BlockFace[] BLOCK_FACES = new BlockFace[]{BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST, BlockFace.UP, BlockFace.DOWN};

    private Set<Location> current = new LinkedHashSet<>();
    private Set<Vector> emptyBlocks = new HashSet<>();
    private Set<Vector> doneBlocks = new HashSet<>();

    private World world;
    private Location pos1;
    private Location pos2;

    private BaseBlock blockType;
    private BaseBlock slabType;
    private static final BaseBlock jukeboxType = BlockTypes.JUKEBOX.getDefaultState().toBaseBlock();
    private static final BaseBlock cobwebType = BlockTypes.COBWEB.getDefaultState().toBaseBlock();
    private static final BaseBlock airType = BlockTypes.AIR.getDefaultState().toBaseBlock();

    @Getter
    private EditSession editSession;

    public Panzern(Player player, Location pos1, Location pos2, Location possibleSource, Material blockMaterial, Material slabMaterial) {
        this.world = pos1.getWorld();
        this.pos1 = new Location(world, Math.min(pos1.getBlockX(), pos2.getBlockX()), Math.min(pos1.getBlockY(), pos2.getBlockY()), Math.min(pos1.getBlockZ(), pos2.getBlockZ()));
        this.pos2 = new Location(world, Math.max(pos1.getBlockX(), pos2.getBlockX()), Math.max(pos1.getBlockY(), pos2.getBlockY()), Math.max(pos1.getBlockZ(), pos2.getBlockZ()));
        if (inRegion(possibleSource)) {
            current.add(possibleSource);
        } else {
            current.add(this.pos1);
            current.add(this.pos2);
        }

        blockType = BlockTypes.get("minecraft:" + blockMaterial.name().toLowerCase()).getDefaultState().toBaseBlock();
        slabType = BlockTypes.get("minecraft:" + slabMaterial.name().toLowerCase()).getDefaultState().toBaseBlock();

        editSession = WorldEditUtils.getEditSession(player);
    }

    public boolean hasNext() {
        return !current.isEmpty();
    }

    @SneakyThrows
    public void next() {
        Location toCheck = current.iterator().next();
        current.remove(toCheck);

        Map<BlockFace, Block> adjacent = new HashMap<>();
        Set<Material> adjacentMaterials = new HashSet<>();
        for (BlockFace blockFace : BLOCK_FACES) {
            Location now = toCheck.clone().add(blockFace.getModX(), blockFace.getModY(), blockFace.getModZ());
            if (inRegion(now)) {
                Block temp = world.getBlockAt(now);
                adjacent.put(blockFace, temp);
                adjacentMaterials.add(temp.getType());
            }
        }
        Block currentBlock = world.getBlockAt(toCheck);

        PanzernResult panzernResult = PanzernResult.DEFAULT;
        for (PanzernAlgorithm panzernAlgorithm : panzernAlgorithmList) {
            PanzernResult temp = panzernAlgorithm.check(currentBlock, adjacent, adjacentMaterials);
            if (temp != null && temp != PanzernResult.DEFAULT) {
                panzernResult = temp;
                break;
            }
        }

        switch (panzernResult) {
            case EMPTY:
                emptyBlocks.add(currentBlock.getLocation().toVector());
                return;
            case SLAB:
                // currentBlock.setType(slabMaterial);
                editSession.setBlock(BukkitAdapter.asBlockVector(currentBlock.getLocation()), slabType);
                break;
            case BLOCK:
            case DEFAULT:
                // currentBlock.setType(blockMaterial);
                editSession.setBlock(BukkitAdapter.asBlockVector(currentBlock.getLocation()), blockType);
                break;
            case UNMOVABLE:
                // currentBlock.setType(Material.JUKEBOX);
                editSession.setBlock(BukkitAdapter.asBlockVector(currentBlock.getLocation()), jukeboxType);
                break;
            case UNMOVABLE_SLAB:
                // currentBlock.setType(Material.COBWEB);
                editSession.setBlock(BukkitAdapter.asBlockVector(currentBlock.getLocation()), cobwebType);
                break;
            default:
                emptyBlocks.add(currentBlock.getLocation().toVector());
                break;
        }
        doneBlocks.add(currentBlock.getLocation().toVector());

        adjacent.forEach((blockFace, temp) -> {
            if (doneBlocks.contains(temp.getLocation().toVector())) {
                return;
            }
            BlockState blockState = editSession.getBlock(BukkitAdapter.asBlockVector(temp.getLocation()));
            boolean isAir = blockState.getBlockType() == BlockTypes.AIR;
            if (isAir && !emptyBlocks.contains(temp.getLocation().toVector())) {
                current.add(temp.getLocation());
            }
        });
    }

    private boolean inRegion(Location location) {
        if (location.getBlockX() >= pos1.getBlockX()) {
            if (location.getBlockY() >= pos1.getBlockY()) {
                if (location.getBlockZ() >= pos1.getBlockZ()) {
                    if (location.getBlockX() <= pos2.getBlockX()) {
                        if (location.getBlockY() <= pos2.getBlockY()) {
                            if (location.getBlockZ() <= pos2.getBlockZ()) {
                                return true;
                            }
                        }
                    }
                }
            }
        }
        return false;
    }

    public int blocksLeft() {
        return current.size();
    }
}
