package AIT_2022.module.complex;

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

import AIT_2022.module.complex.amb.CivMessages;
import AIT_2022.module.complex.common.EntityFilter;
import AIT_2022.module.complex.common.Stucked;
import adf.core.agent.communication.standard.bundle.centralized.CommandAmbulance;
import adf.core.agent.communication.standard.bundle.centralized.CommandFire;
import adf.core.agent.communication.standard.bundle.centralized.CommandPolice;
import adf.core.agent.communication.standard.bundle.information.MessageAmbulanceTeam;
import adf.core.component.module.complex.HumanDetector;
import adf.core.component.communication.CommunicationMessage;
import adf.core.component.module.algorithm.*;
import adf.core.agent.info.*;
import adf.core.agent.module.ModuleManager;
import adf.core.agent.develop.DevelopData;
import adf.core.agent.communication.MessageManager;
import rescuecore2.worldmodel.EntityID;
import rescuecore2.standard.entities.*;

import static rescuecore2.standard.entities.StandardEntityURN.*;

public class AITAtDetector extends HumanDetector {
    private PathPlanning pathPlanner;
    private Clustering clusterer;
    private Clustering stuckedHumans;
    private CivMessages civMessages;
    private EntityID result = null;
    private Set<EntityID> cluster = new HashSet<>();

    // private static final double AGENT_CAN_MOVE = 7000.0;

    private final boolean VOICE = false;
    private final boolean RADIO = true;
    private boolean followCommand = false;

    public AITAtDetector(
        AgentInfo ai, WorldInfo wi, ScenarioInfo si,
        ModuleManager mm, DevelopData dd)
    {
        super(ai, wi, si, mm, dd);

        this.pathPlanner = mm.getModule(
            "ActionTransport.PathPlanning",
            "adf.sample.module.algorithm.SamplePathPlanning");
        this.registerModule(this.pathPlanner);

        this.clusterer = mm.getModule(
            "SampleHumanDetector.Clustering",
            "adf.sample.module.algorithm.SampleKMeans");
        this.registerModule(this.clusterer);

        this.stuckedHumans =
                mm.getModule("AIT_2022.module.algorithm.StuckedHumans");
        this.registerModule(this.stuckedHumans);

        this.civMessages = mm.getModule("AIT.AMB.CivMessages");
        this.registerModule(this.civMessages);
    }

    @Override
    public EntityID getTarget() {
        return this.result;
    }

    @Override
    public HumanDetector updateInfo(MessageManager mm) {
        super.updateInfo(mm);
        if (this.getCountUpdateInfo() > 1) return this;

        if (this.cluster.isEmpty()) this.initCluster();

        this.receiveMessage(mm);
        this.sendMessage(mm); 

        return this;
    }

    @Override
    public HumanDetector calc() {
        this.civMessages.calc();

        final Human onboard = this.agentInfo.someoneOnBoard();
        if (onboard != null) {
            this.result = onboard.getID();
            return this;
        }

        final Set<EntityID> change 
                = this.worldInfo.getChanged().getChangedEntities();
        if (this.followCommand) {
            if (change.contains(this.result)) {
                this.followCommand = false;
            } else {
                return this;
            }
        }

        this.result = null;

        for (EntityID e : change) {
            StandardEntity se = this.worldInfo.getEntity(e);
            if (!(se instanceof Civilian)) continue;
            Civilian c = (Civilian)se;
            if (this.worldInfo.getPosition(c).getStandardURN().equals(REFUGE)) continue;
            if (c.getBuriedness() > 0 || c.getHP() == 0 || c.getDamage() == 0) continue;
            this.result = c.getID();
            break;
        } 

        return this;
    }

    private void sendMessage(MessageManager mm) {
        final EntityFilter filter 
                = new EntityFilter(this.worldInfo, this.agentInfo);
        final Set<Blockade> blockades = filter.getBlockades(); 
        final Stucked Stucked = new Stucked(
                blockades, this.agentInfo.getX(), this.agentInfo.getY());
        final EntityID positionID = this.agentInfo.getPosition();
        if (Stucked.isStucked()) {
            final EntityID toID = filter.getNearyPF();
            mm.addMessage(new CommandPolice(
                    RADIO, toID, positionID, CommandPolice.ACTION_CLEAR));
            mm.addMessage(new CommandPolice(
                    VOICE, toID, positionID, CommandPolice.ACTION_CLEAR));
            // System.out.println(String.format("[AT_Stucked(%3d):%10d] toID(%10d)",
            //         this.agentInfo.getTime(),
            //         this.agentInfo.getID(),
            //         toID == null ? -1 : toID.getValue()));
            return;
        } else if (this.isMyselfBuried()) {
            final EntityID toID = filter.getNearyFB();
            mm.addMessage(new CommandFire(
                    RADIO, toID, positionID, CommandFire.ACTION_RESCUE));
            mm.addMessage(new CommandFire(
                    VOICE, toID, positionID, CommandFire.ACTION_RESCUE));
            // System.out.println(String.format("[AT_Buryed(%3d):%10d] toID(%10d)",
            //         this.agentInfo.getTime(),
            //         this.agentInfo.getID(),
            //         toID == null ? -1 : toID.getValue()));
            return;
        }
    }

    private boolean isMyselfBuried() {
        EntityID myID = this.agentInfo.getID();
        Human me = (Human) this.worldInfo.getEntity(myID);
        if (!(this.agentInfo.getPositionArea() instanceof Building)) return false;
        if (!me.isBuriednessDefined()) return false;
        return me.getBuriedness() != 0;
    }

    private void initCluster() {
        this.clusterer.calc();

        final EntityID me = this.agentInfo.getID();
        final int index = this.clusterer.getClusterIndex(me);
        final Collection<EntityID> ids =
            this.clusterer.getClusterEntityIDs(index);
        this.cluster.addAll(ids);
    }

    private boolean isLoadTask(EntityID e) {
        final Human h = (Human)this.worldInfo.getEntity(e);
        if (!(h instanceof Civilian)) return false;
        return h.getHP() > 0 && h.getBuriedness() == 0;
    }

    private void handleMessage(CommandAmbulance msg) {
        if (msg.getToID().getValue() 
            - this.agentInfo.getID().getValue() != 0) return;
        if (msg.getAction() == CommandAmbulance.ACTION_LOAD) {
            if (this.agentInfo.someoneOnBoard() == null) {
                this.followCommand = true;
                this.result = msg.getTargetID();
            }
        }
    }

    private void receiveMessage(MessageManager mm) {
        final List<CommunicationMessage> cmdAmb =
                mm.getReceivedMessageList(CommandAmbulance.class);
        for (CommunicationMessage tmp : cmdAmb) {
            final CommandAmbulance message = (CommandAmbulance) tmp;
            this.handleMessage(message);
        }
    }
}
