/*
 *   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.fightsystem.ai;

import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.listener.Chat;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.OneShotStateDependent;
import de.steamwar.fightsystem.utils.Region;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Note;
import org.bukkit.block.Block;
import org.bukkit.block.Lectern;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Openable;
import org.bukkit.block.data.Powerable;
import org.bukkit.block.data.type.Comparator;
import org.bukkit.block.data.type.NoteBlock;
import org.bukkit.block.data.type.Repeater;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.Vector;

import java.util.*;
import java.util.logging.Level;

public abstract class AI {

    private static final Map<UUID, AI> ais = new HashMap<>();

    static {
        new OneShotStateDependent(ArenaMode.All, FightState.Spectate, () -> {
            ais.values().forEach(AI::stop);
            ais.clear();
        });
    }

    public static AI getAI(UUID uuid) {
        return ais.get(uuid);
    }

    private final FightTeam team;
    private final LivingEntity entity;
    private final BukkitTask task;
    private final Queue<Action> queue = new ArrayDeque<>();

    protected AI(FightTeam team, SteamwarUser user) {
        this.team = team;

        entity = (LivingEntity) Config.world.spawnEntity(Config.SpecSpawn, EntityType.VILLAGER);
        entity.setCustomName(user.getUserName());
        ((Villager)entity).setAware(false);

        task = Bukkit.getScheduler().runTaskTimer(FightSystem.getPlugin(), this::run, 1, 1);
        ais.put(entity.getUniqueId(), this);
        team.addMember(entity, user);
    }

    public abstract SchematicNode chooseSchematic();

    public boolean acceptJoinRequest(Player player, FightTeam team) {
        return true;
    }

    protected abstract void plan();

    public void stop() {
        if(!entity.isDead())
            entity.remove();

        if(!task.isCancelled())
            task.cancel();
    }

    public LivingEntity getEntity() {
        return entity;
    }

    protected void setReady() {
        if(FightState.getFightState() != FightState.POST_SCHEM_SETUP)
            return;

        if(team.getLeader().getEntity() != entity)
            return;

        team.setReady(true);
    }

    protected void chat(String message) {
        FightSystem.getPlugin().getLogger().log(Level.INFO, () -> entity.getName() + "» " + message);
        Chat.broadcastChat("PARTICIPANT_CHAT", team.getColoredName(), entity.getName(), message);
    }

    protected Vector getPosition() {
        Location location = entity.getLocation();
        Region extend = team.getExtendRegion();
        if(Fight.getUnrotated() == team)
            return new Vector(
                    location.getX() - extend.getMinX(),
                    location.getY() - team.getSchemRegion().getMinY(),
                    location.getZ() - extend.getMinZ()
            );
        else
            return new Vector(
                    extend.getMaxX() - location.getX(),
                    location.getY() - team.getSchemRegion().getMinY(),
                    extend.getMaxZ() - location.getZ()
            );
    }

    protected Material getBlock(Vector pos) {
        queue.add(new Action(1));
        return translate(pos, true).getBlock().getType();
    }

    protected boolean isPowered(Vector pos) {
        queue.add(new Action(1));
        return translate(pos, true).getBlock().isBlockPowered();
    }

    protected void setTNT(Vector pos) {
        queue.add(new Action(1) {
            @Override
            public void run() {
                if(FightState.getFightState() != FightState.RUNNING)
                    return;

                Location location = translate(pos, true);
                if(interactionDistanceViolation(location))
                    return;

                Block block = location.getBlock();
                if(block.getType() == Material.AIR)
                    block.setType(Material.TNT);
            }
        });
    }

    protected void interact(Vector pos) {
        queue.add(new Action(1) {
            @Override
            public void run() {
                Location location = translate(pos, true);
                if(interactionDistanceViolation(location))
                    return;

                interact(location.getBlock());
            }
        });
    }

    protected void interact(Vector pos, int n) {
        queue.add(new Action(1) {
           @Override
           public void run() {
               Location location = translate(pos, true);
               if (interactionDistanceViolation(location))
                   return;
               Block block = location.getBlock();
               BlockData data =  block.getBlockData();
               if (data instanceof Repeater) {
                   Repeater repeater = (Repeater) data;
                   repeater.setDelay(n);
               } else if (data instanceof Lectern) {
                   Lectern lectern = (Lectern) data;
                   lectern.setPage(n);
               }
               block.setBlockData(data);
           }
        });
    }

    protected void move(Vector pos) {
        queue.add(new Action(2) {
            @Override
            public void run() {
                Location location = entity.getLocation();
                Location target = translate(pos, false);
                if(Math.abs(location.getX() - target.getX()) > 1 || Math.abs(location.getY() - target.getY()) > 1.2 || Math.abs(location.getZ() - target.getZ()) > 1) {
                    FightSystem.getPlugin().getLogger().log(Level.INFO, () -> entity.getName() + ": Overdistance movement " + location.toVector() + " " + target.toVector());
                    return;
                }

                if(!team.getFightPlayer(entity).canEntern() && !team.getExtendRegion().inRegion(target))
                    return;

                entity.teleport(target, PlayerTeleportEvent.TeleportCause.COMMAND);
            }
        });
    }

    private boolean interactionDistanceViolation(Location location) {
        return location.distance(entity.getEyeLocation()) > 5;
    }

    private void interact(Block block) {
        BlockData data = block.getBlockData(); //TODO only 1.14+ compatible at the moment
        if (data instanceof NoteBlock) {
            NoteBlock noteBlock = (NoteBlock) data;
            Note note = noteBlock.getNote();
            noteBlock.setNote(note.isSharped() ? note.flattened() : note.sharped());
        } else if (data instanceof Openable) {
            Openable openable = (Openable) data;
            openable.setOpen(!openable.isOpen());
        } else if (data instanceof Comparator) {
            Comparator comparator = (Comparator) data;
            comparator.setMode(Comparator.Mode.values()[1 - comparator.getMode().ordinal()]);
        } else if (data instanceof Powerable) {
            Material type = block.getType();
            Powerable powerable = (Powerable) data;
            boolean isPowered = powerable.isPowered();

            if(type.name().endsWith("BUTTON")) {
                if(isPowered)
                    return;

                Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), () -> {
                    if(!block.getType().name().endsWith("BUTTON"))
                        return;

                    powerable.setPowered(false);
                    block.setBlockData(powerable);
                }, type.name().endsWith("STONE_BUTTON") ? 20 : 30);
            }

            powerable.setPowered(!isPowered);
        }
        block.setBlockData(data);
    }

    private void run() {
        if(queue.isEmpty())
            plan();

        if(!queue.isEmpty() && --queue.peek().delay == 0)
            queue.poll().run();
    }

    private Location translate(Vector pos, boolean blockPos) {
        Region extend = team.getExtendRegion();
        if(Fight.getUnrotated() == team)
            return new Location(
                    Config.world,
                    pos.getX() + extend.getMinX(),
                    pos.getY() + team.getSchemRegion().getMinY(),
                    pos.getZ() + extend.getMinZ()
            );
        else
            return new Location(
                    Config.world,
                    extend.getMaxX() - pos.getX() - (blockPos ? 1 : 0),
                    pos.getY() + team.getSchemRegion().getMinY(),
                    extend.getMaxZ() - pos.getZ() - (blockPos ? 1 : 0)
            );
    }

    private static class Action {
        private int delay;
        public Action(int delay) {
            this.delay = delay;
        }

        public void run() {}
    }
}
