/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2025 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.laufbau.states;

import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockTypes;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.slaves.laufbau.BlockBoundingBox;
import de.steamwar.bausystem.features.slaves.laufbau.Cuboid;
import de.steamwar.bausystem.region.Point;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;

import java.util.*;

public class CreatingLaufState implements LaufbauState {

    private static final Cuboid ONE = new Cuboid(0, 0, 0, 1, 1, 1);
    private static final Cuboid ABOVE_HALF = new Cuboid(0, 1, 0, 1, 0.5, 1);
    private static final BaseBlock SHELL;

    static {
        SHELL = BlockTypes.get("minecraft:end_stone").getDefaultState().toBaseBlock();
    }

    private long start = System.currentTimeMillis();

    private List<Point> blocks;
    private Map<Point, Set<Cuboid>> cuboidIntersectionCache;
    private List<Point> outerPoints = new ArrayList<>();
    private Set<Point> innerPoints = new HashSet<>();
    private World world;
    private EditSession editSession;
    private List<BlockBoundingBox> elements;
    private int factor;

    private int operations = 0;
    private int totalOperations;

    public CreatingLaufState(Set<Point> blocks, Map<Point, Set<Cuboid>> cuboidIntersectionCache, World world, EditSession editSession, List<BlockBoundingBox> elements, int factor) {
        this.blocks = new ArrayList<>(blocks);
        this.cuboidIntersectionCache = cuboidIntersectionCache;
        this.world = world;
        this.editSession = editSession;
        this.elements = elements;
        this.factor = factor;

        totalOperations = blocks.size() * 2;
    }

    @Override
    public String actionBarMessage(Player p) {
        return BauSystem.MESSAGE.parse("LAUFBAU_SIMPLE_PROGRESS", p, BauSystem.MESSAGE.parse("LAUFBAU_STATE_CREATE_LAUF", p), operations, totalOperations, eta(p, start, operations, totalOperations));
    }

    @Override
    public boolean hasNext() {
        return !blocks.isEmpty() || !outerPoints.isEmpty();
    }

    @Override
    public void next() {
        operations++;
        if (!blocks.isEmpty()) {
            Point point = blocks.remove(0);
            boolean isInCuboid = false;
            Cuboid tempCuboid = ONE.add(point);
            Cuboid aboveHalfTempCuboid = ABOVE_HALF.add(point);
            List<Cuboid> intersectedCuboids = new ArrayList<>();
            for (Cuboid cuboid : cuboidIntersectionCache.get(point.divide(factor))) {
                if (cuboid.intersects(tempCuboid)) {
                    isInCuboid = true;
                    intersectedCuboids.add(cuboid);
                }
                if (cuboid.intersects(aboveHalfTempCuboid)) {
                    intersectedCuboids.add(cuboid);
                }
            }
            if (!isInCuboid) {
                if (hasInnerNeighbour(point)) {
                    operations++;
                    setShell(point);
                } else {
                    outerPoints.add(point);
                }
            } else {
                innerPoints.add(point);
                operations++;
                calcInnerBlock(point, intersectedCuboids);
            }
        } else {
            Point point = outerPoints.remove(0);
            setShell(point);
        }
    }

    private void calcInnerBlock(Point point, List<Cuboid> neededCuboids) {
        BlockBoundingBox highestBlockBoundingBox = null;
        for (BlockBoundingBox blockDataWithBoundingBox : elements) {
            boolean isInCuboid = false;
            for (Cuboid cuboid : neededCuboids) {
                for (Cuboid currentCuboid : blockDataWithBoundingBox.getCuboidList()) {
                    if (cuboid.intersects(currentCuboid.add(point))) {
                        isInCuboid = true;
                        break;
                    }
                }
                if (isInCuboid) {
                    break;
                }
            }
            if (!isInCuboid) {
                highestBlockBoundingBox = blockDataWithBoundingBox;
                break;
            }
        }
        if (highestBlockBoundingBox == null) {
            return;
        }

        Location location = new Location(world, point.getX(), point.getY(), point.getZ());
        if (!location.getBlock().getType().isAir()) {
            return;
        }
        try {
            BaseBlock block = BukkitAdapter.adapt(highestBlockBoundingBox.blockData).toBaseBlock();
            if (highestBlockBoundingBox.blockConsumer != null) highestBlockBoundingBox.blockConsumer.accept(block);
            editSession.setBlock(BukkitAdapter.asBlockVector(location), block);
        } catch (MaxChangedBlocksException e) {
            e.printStackTrace();
            // ignored
        }
    }

    private boolean hasInnerNeighbour(Point point) {
        Point p1 = new Point(point.getX() - 1, point.getY(), point.getZ());
        if (innerPoints.contains(p1)) {
            return true;
        }
        Point p2 = new Point(point.getX() + 1, point.getY(), point.getZ());
        if (innerPoints.contains(p2)) {
            return true;
        }
        Point p3 = new Point(point.getX(), point.getY() - 1, point.getZ());
        if (innerPoints.contains(p3)) {
            return true;
        }
        Point p4 = new Point(point.getX(), point.getY() + 1, point.getZ());
        if (innerPoints.contains(p4)) {
            return true;
        }
        Point p5 = new Point(point.getX(), point.getY(), point.getZ() - 1);
        if (innerPoints.contains(p5)) {
            return true;
        }
        Point p6 = new Point(point.getX(), point.getY(), point.getZ() + 1);
        if (innerPoints.contains(p6)) {
            return true;
        }
        return false;
    }

    private void setShell(Point point) {
        if (hasInnerNeighbour(point)) {
            Location location = new Location(world, point.getX(), point.getY(), point.getZ());
            if (!location.getBlock().getType().isAir()) {
                return;
            }
            try {
                editSession.setBlock(BukkitAdapter.asBlockVector(location), SHELL);
                // location.getBlock().setType(Material.END_STONE, false);
            } catch (MaxChangedBlocksException e) {
                e.printStackTrace();
                // ingored
            }
        }
    }

    @Override
    public LaufbauState getNextState() {
        return null;
    }
}
