package autumn_2023.centralized;

import adf.core.agent.communication.MessageManager;
import adf.core.agent.communication.standard.bundle.StandardMessagePriority;
import adf.core.agent.communication.standard.bundle.centralized.CommandFire;
import adf.core.agent.communication.standard.bundle.information.MessageCivilian;
import adf.core.agent.develop.DevelopData;
import adf.core.agent.info.AgentInfo;
import adf.core.agent.info.ScenarioInfo;
import adf.core.agent.info.WorldInfo;
import adf.core.agent.module.ModuleManager;
import adf.core.component.centralized.CommandExecutor;
import adf.core.component.module.algorithm.PathPlanning;
import autumn_2023.module.comm.infomation.MessageCost;
import rescuecore2.standard.entities.*;
import rescuecore2.worldmodel.EntityID;

import java.util.*;
import java.util.stream.Collectors;

import static rescuecore2.standard.entities.StandardEntityURN.AMBULANCE_TEAM;
import static rescuecore2.standard.entities.StandardEntityURN.REFUGE;

public class AuctionCommandExecutorFire extends CommandExecutor<CommandFire> {

    /*
    救急司令所からすでに受け取った埋没市民のIDの集合
     */
    private final Set<EntityID> bidCentreCivilians = new HashSet<>();
    private final PathPlanning pathPlanning;

    public AuctionCommandExecutorFire(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) {
        super(ai, wi, si, moduleManager, developData);

        this.pathPlanning = moduleManager.getModule("SampleHumanDetector.PathPlanning",
                "adf.impl.module.algorithm.DijkstraPathPlanning");
    }

    @Override
    public CommandExecutor<CommandFire> updateInfo(MessageManager messageManager) {
        super.updateInfo(messageManager);

        // FireStationから受信したMessageCivilianを収集
        final Set<EntityID> receivedCentreCivilians = messageManager.getReceivedMessageList(MessageCivilian.class).parallelStream()
                .map(MessageCivilian.class::cast)
                .filter(e -> this.worldInfo.getEntity(e.getSenderID()) instanceof FireStation)
                .map(MessageCivilian::getAgentID)
                .filter(e -> !this.bidCentreCivilians.contains(e))
                .collect(Collectors.toSet());

        // 新規メッセージクラスを用いてタスク実行時のコストを救急司令所に送信
        receivedCentreCivilians.parallelStream()
                .map(e -> new MessageCost(true, StandardMessagePriority.HIGH, e, CalculationCost(e)))
                .forEach(messageManager::addMessage);

        // 知覚した救助対象の市民の情報をMessageCivilianとして指令所に送信
        this.worldInfo.getChanged().getChangedEntities().parallelStream()
                .filter(this::isRescueTarget)
                .map(this.worldInfo::getEntity)
                .filter(Civilian.class::isInstance)
                .map(Civilian.class::cast)
                .map(e -> new MessageCivilian(true, StandardMessagePriority.HIGH, e))
                .forEach(messageManager::addMessage);

        this.bidCentreCivilians.addAll(receivedCentreCivilians);

        return this;
    }

    @Override
    public CommandExecutor<CommandFire> setCommand(CommandFire command) {
        // TODO
        return this;
    }

    @Override
    public CommandExecutor<CommandFire> calc() {
        // TODO
        return this;
    }

    /**
     * 対象が救助活動の対象であるか否かを判定するメソッド
     * @param entity 対象のエンティティ
     * @return entityが救助活動の対象であるか否か
     */
    private boolean isValidHuman(StandardEntity entity) {
        if (entity == null)
            return false;
        if (!(entity instanceof Human target))
            return false;

        if (!target.isHPDefined() || target.getHP() == 0)
            return false;
        if (!target.isPositionDefined())
            return false;
        if (!target.isDamageDefined() || target.getDamage() == 0)
            return false;
        if (!target.isBuriednessDefined())
            return false;

        StandardEntity position = worldInfo.getPosition(target);
        if (position == null)
            return false;

        StandardEntityURN positionURN = position.getStandardURN();
        return positionURN != REFUGE && positionURN != AMBULANCE_TEAM;
    }

    /**
     * 消防隊が対象を救出をするためのコストを計算するメソッド
     * @param target 埋没したエージェントのID
     * @return targetへ移動するためのステップ数
     */
    private int CalculationCost(EntityID target){
        double targetDistance = this.pathPlanning.getDistance(this.agentInfo.getID(), target);
        return (int)(Math.ceil(targetDistance / 40000));
    }

    /**
     * 対象が消防隊による救出の対象であるか否かを判定するメソッド
     * @param target 判定対象のエンティティID
     * @return targetが救出対象であるか否か
     */
    private boolean isRescueTarget(EntityID target) {
        final StandardEntity targetEntity = this.worldInfo.getEntity(target);
        if (!isValidHuman(targetEntity))
            return false;

        final Human targetHuman = (Human) targetEntity;
        return targetHuman.getBuriedness() > 0;
    }
}
