package autumn_2023.centralized;

import adf.core.agent.action.common.ActionMove;
import adf.core.agent.action.common.ActionRest;
import adf.core.agent.communication.MessageManager;
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.agent.precompute.PrecomputeData;
import adf.core.component.centralized.CommandExecutor;
import adf.core.component.extaction.ExtAction;
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> {

    private static final int ACTION_UNKNOWN = -1;
    private static final int ACTION_REST = CommandFire.ACTION_REST;
    private static final int ACTION_MOVE = CommandFire.ACTION_MOVE;
    private static final int ACTION_RESCUE = CommandFire.ACTION_RESCUE;
    private static final int ACTION_AUTONOMY = CommandFire.ACTION_AUTONOMY;

    private PathPlanning pathPlanning;

    private ExtAction actionFireRescue;
    private ExtAction actionExtMove;

    private int commandType;
    private EntityID target;
    private EntityID commanderID;

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

    public AuctionCommandExecutorFire(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) {
        super(ai, wi, si, moduleManager, developData);
        this.commandType = ACTION_UNKNOWN;
        switch (scenarioInfo.getMode()) {
            case PRECOMPUTATION_PHASE:
            case PRECOMPUTED:
            case NON_PRECOMPUTE:
              this.pathPlanning = moduleManager.getModule(
                  "DefaultCommandExecutorFire.PathPlanning",
                  "adf.impl.module.algorithm.DijkstraPathPlanning");
              this.actionFireRescue = moduleManager.getExtAction(
                  "DefaultCommandExecutorFire.ExtActionFireRescue",
                  "adf.impl.extaction.DefaultExtActionFireRescue");
              this.actionExtMove = moduleManager.getExtAction(
                  "DefaultCommandExecutorFire.ExtActionMove",
                  "adf.impl.extaction.DefaultExtActionMove");
              break;
        }
    }

    @Override
    public CommandExecutor<CommandFire> setCommand(CommandFire command) {
        EntityID agentID = this.agentInfo.getID();
        if (command.isToIDDefined() && Objects.requireNonNull(command.getToID()).getValue() == agentID.getValue()) 
        {
            this.commandType = command.getAction();
            this.target = command.getTargetID();
            this.commanderID = command.getSenderID();
        }

        return this;
    }

    @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());

        // 新規メッセージクラスを用いてタスク実行時のコストを救急司令所に送信
        if (this.commandType == -1) 
        {
            receivedCentreCivilians.parallelStream()
                .map(e -> new MessageCost(true, 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, e))
                .forEach(messageManager::addMessage);

        this.bidCentreCivilians.addAll(receivedCentreCivilians);

        return this;
    }

    @Override
    public CommandExecutor<CommandFire> precompute(PrecomputeData precomputeData) 
    {
        super.precompute(precomputeData);
        if (this.getCountPrecompute() >= 2) return this;
        this.pathPlanning.precompute(precomputeData);
        this.actionFireRescue.precompute(precomputeData);
        this.actionExtMove.precompute(precomputeData);

        return this;
    }

    @Override
    public CommandExecutor<CommandFire> resume(PrecomputeData precomputeData) 
    {
        super.resume(precomputeData);
        if (this.getCountResume() >= 2) return this;
        this.pathPlanning.resume(precomputeData);
        this.actionFireRescue.resume(precomputeData);
        this.actionExtMove.resume(precomputeData);

        return this;
    }

    @Override
    public CommandExecutor<CommandFire> preparate() 
    {
        super.preparate();
        if (this.getCountPreparate() >= 2) return this;
        this.pathPlanning.preparate();
        this.actionFireRescue.preparate();
        this.actionExtMove.preparate();
        return this;
    }

    @Override
    public CommandExecutor<CommandFire> calc() {
        this.result = null;
        switch (this.commandType) 
        {
        case ACTION_REST:
            EntityID position = this.agentInfo.getPosition();
            if (this.target == null) 
            {
                Collection<EntityID> refuges = this.worldInfo.getEntityIDsOfType(REFUGE);
                if (refuges.contains(position)) 
                {
                    this.result = new ActionRest();
                } else {
                    this.pathPlanning.setFrom(position);
                    this.pathPlanning.setDestination(refuges);
                    List<EntityID> path = this.pathPlanning.calc().getResult();
                    if (path != null && path.size() > 0) 
                    {
                        this.result = new ActionMove(path);
                    } else {
                        this.result = new ActionRest();
                    }
                }
                return this;
            }
            if (position.getValue() != this.target.getValue()) 
            {
                List<EntityID> path = this.pathPlanning.getResult(position, this.target);
                if (path != null && path.size() > 0) 
                {
                    this.result = new ActionMove(path);
                    return this;
                }
            }
            this.result = new ActionRest();

            return this;

        case ACTION_MOVE:
            if (this.target != null) 
            {
                this.result = this.actionExtMove.setTarget(this.target).calc().getAction();
            }

            return this;

        case ACTION_RESCUE:
            if (this.target != null) 
            {
                this.result = this.actionFireRescue.setTarget(this.target).calc().getAction();
            }

            return this;

        case ACTION_AUTONOMY:
            if (this.target == null) 
            {
                return this;
            }
            StandardEntity targetEntity = this.worldInfo.getEntity(this.target);
            if (targetEntity instanceof Area) 
            {
            this.result = this.actionExtMove.setTarget(this.target).calc().getAction();
            } else if (targetEntity instanceof Human) 
            {
                this.result = this.actionFireRescue.setTarget(this.target).calc().getAction();
            }
        }

        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;
    }
}
