/*
 *
 *     This file is a part of the SteamWar software.
 *
 *     Copyright (C) 2020  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.tracer.show.mode;

import de.steamwar.bausystem.CraftbukkitWrapper;
import de.steamwar.bausystem.FlatteningWrapper;
import de.steamwar.bausystem.tracer.AbstractTraceEntity;
import de.steamwar.bausystem.tracer.RoundedTNTPosition;
import de.steamwar.bausystem.tracer.TNTPosition;
import de.steamwar.bausystem.tracer.show.ShowMode;
import de.steamwar.bausystem.tracer.show.ShowModeParameter;
import org.bukkit.entity.Player;
import org.bukkit.util.Consumer;
import org.bukkit.util.Vector;

import java.util.HashMap;
import java.util.Map;

public class EntityShowMode implements ShowMode {

    protected final Player player;
    protected final ShowModeParameter showModeParameter;

    private final Map<RoundedTNTPosition, AbstractTraceEntity> tntEntityMap = new HashMap<>();
    private final Map<RoundedTNTPosition, AbstractTraceEntity> updateEntityMap = new HashMap<>();

    public EntityShowMode(Player player, ShowModeParameter showModeParameter) {
        this.player = player;
        this.showModeParameter = showModeParameter;
    }

    @Override
    public void show(TNTPosition position) {
        if (!showModeParameter.isWater() && position.isExploded() && checkWater(position.getLocation())) {
            // Basic
            for (TNTPosition pos : position.getRecord().getPositions()) {
                RoundedTNTPosition roundedTNTPosition = new RoundedTNTPosition(pos);
                tntEntityMap.computeIfPresent(roundedTNTPosition, (p, tnt) -> {
                    return tnt.hide(player, false) ? null : tnt;
                });
            }
            // Advanced
            for (TNTPosition pos : position.getRecord().getPositions()) {
                applyOnPosition(pos, updatePointPosition -> {
                    updateEntityMap.computeIfPresent(new RoundedTNTPosition(updatePointPosition), (p, point) -> {
                        return point.hide(player, false) ? null : point;
                    });
                });
            }
            return;
        }

        RoundedTNTPosition roundedTNTPosition = new RoundedTNTPosition(position);
        AbstractTraceEntity entity = tntEntityMap.computeIfAbsent(roundedTNTPosition, pos -> createEntity(player, position.getLocation(), true));
        entity.display(player, position.isExploded());

        applyOnPosition(position, updatePointPosition -> {
            updateEntityMap.computeIfAbsent(new RoundedTNTPosition(updatePointPosition), pos -> {
                return createEntity(player, updatePointPosition, false);
            }).display(player, position.isExploded());
        });
    }

    private boolean checkWater(Vector position) {
        return FlatteningWrapper.impl.inWater(player.getWorld(), position);
    }

    public static AbstractTraceEntity createEntity(Player player, Vector position, boolean tnt) {
        return CraftbukkitWrapper.impl.create(player.getWorld(), position, tnt);
    }

    private void applyOnPosition(TNTPosition position, Consumer<Vector> function) {
        if (position.getPreviousLocation() == null) return;

        if (showModeParameter.isInterpolate_Y()) {
            Vector updatePointY = position.getPreviousLocation().clone().setY(position.getLocation().getY());
            if (!position.getLocation().equals(updatePointY)) {
                function.accept(updatePointY);
            }
        }

        if (showModeParameter.isInterpolate_XZ()) {
            Vector movement = position.getLocation().clone().subtract(position.getPreviousLocation());
            Vector updatePointXZ = Math.abs(movement.getX()) > Math.abs(movement.getZ())
                    ? position.getLocation().clone().setZ(position.getPreviousLocation().getZ())
                    : position.getLocation().clone().setX(position.getPreviousLocation().getX());
            if (!position.getLocation().equals(updatePointXZ)) {
                function.accept(updatePointXZ);
            }
        }
    }

    @Override
    public void hide() {
        tntEntityMap.forEach((roundedTNTPosition, abstractTraceEntity) -> abstractTraceEntity.hide(player, true));
        tntEntityMap.clear();
        updateEntityMap.forEach((roundedTNTPosition, abstractTraceEntity) -> abstractTraceEntity.hide(player, true));
        updateEntityMap.clear();
    }

}
