package AITB_2022.module.complex;

import adf.core.agent.communication.MessageManager;
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.module.algorithm.Clustering;
import adf.core.component.module.algorithm.PathPlanning;
import adf.core.component.module.complex.Search;
import rescuecore2.standard.entities.AmbulanceTeam;
import rescuecore2.standard.entities.Building;
import rescuecore2.standard.entities.Civilian;
import rescuecore2.standard.entities.Human;
import rescuecore2.standard.entities.StandardEntity;
import rescuecore2.standard.entities.StandardEntityURN;
import rescuecore2.worldmodel.EntityID;
import rescuecore2.standard.messages.AKSpeak;
import rescuecore2.messages.Command;
import rescuecore2.Timestep;

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

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

public class AITSearchAmbulance extends Search {
    private EntityID result;
    private Clustering clustering;
    // HashSetは，要素の重複を許可しない集合構造を表す．また，順番も持ちません．
    private Set<EntityID> candidates = new HashSet<>();// 探索対象
    private Set<EntityID> hold = new HashSet<>();// 探索を保留
    private Set<EntityID> searched = new HashSet<>();// 探索済
    private PathPlanning pathPlanning;
    private Set<EntityID> agents = new HashSet<>();
    private StandardEntity nowpos;
    private StandardEntity lastpos = null;

    List<EntityID> cluster_list = new ArrayList<>();

    // 救急隊がすでに通信で情報が送信された市民のIDを保持するリスト
    List<EntityID> sentBuriedCivilianIDs = new ArrayList<>();

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

        String clusteringKey = "SampleSearch.Clustering";
        switch (ai.me().getStandardURN()) {
            case FIRE_BRIGADE:
                clusteringKey += ".Fire";
                break;
            case AMBULANCE_TEAM:
                clusteringKey += ".Ambulance";
                break;
            case POLICE_FORCE:
                clusteringKey += ".Police";
                break;
        }

        this.clustering = mm.getModule(clusteringKey, "adf.impl.module.algorithm.SampleKMeans");
        this.registerModule(this.clustering);

        this.pathPlanning = mm.getModule("SampleSearch.PathPlanning.Ambulance",
                "adf.impl.module.algorithm.DijkstraPathPlanning");
        this.registerModule(this.pathPlanning);

        agents.addAll(this.worldInfo.getEntityIDsOfType(FIRE_BRIGADE));
        agents.addAll(this.worldInfo.getEntityIDsOfType(POLICE_FORCE));
        agents.addAll(this.worldInfo.getEntityIDsOfType(AMBULANCE_TEAM));
        agents.addAll(this.worldInfo.getEntityIDsOfType(FIRE_STATION));
        agents.addAll(this.worldInfo.getEntityIDsOfType(POLICE_OFFICE));
        agents.addAll(this.worldInfo.getEntityIDsOfType(AMBULANCE_CENTRE));

    }

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

    // MessageManager メッセージの統括・管理をおこなうコンポーネット
    @Override
    public Search updateInfo(MessageManager mm) {
        super.updateInfo(mm);

        if (this.getCountUpdateInfo() > 1)
            return this;


        if (this.worldInfo.getEntity(((AmbulanceTeam) (this.agentInfo.me())).getPosition())
                .getStandardURN() == BUILDING) {
            this.worldInfo.getEntitiesOfType(CIVILIAN)
                    .stream()
                    .filter(Civilian.class::isInstance)
                    .map(Civilian.class::cast)
                    .filter(c -> !sentBuriedCivilianIDs.contains(c.getID()))
                    .filter(c -> c.getPosition().equals(this.agentInfo.getPosition()))
                    .filter(c -> c.isBuriednessDefined())
                    .filter(c -> c.getBuriedness() > 0)
                    .forEach(c -> {
                        mm.addMessage(new MessageCivilian(true, c));
                        mm.addMessage(new MessageCivilian(false, c));
                    });
        }

        this.updateSearched();
        this.updateCandidate();

        if (this.candidates.isEmpty()) {
            this.initCandidates();
        }

        return this;
    }

    @Override
    public Search calc() {
        EntityID myID = this.agentInfo.getID();
        EntityID nearestID = null;
        int nearest = Integer.MAX_VALUE;
        int distance;

        if (((Human) this.worldInfo.getEntity(myID)).getBuriedness() > 0)
            return this;
        // System.out.println("Search"+myID);

        nowpos = this.worldInfo.getPosition(myID);
        // 移動していない時，targetを変更
        if (this.result != null && !this.isReach() && nowpos == lastpos) {
            // System.out.println("change target");
            hold.add(this.result);
            this.result = null;
        }
        lastpos = nowpos;

        if (this.result != null && !this.isReach()) {
            return this;
        }

        if (this.candidates.isEmpty())
            this.candidates.addAll(hold);
        if (this.candidates.isEmpty() && this.clustering != null) {
            // System.out.println("[Ambulance] new cluster");
            // クラスター番号が＋1のクラスタ（だめなら-1）を取得する
            int idx = this.clustering.getClusterIndex(myID);
            if (this.clustering.getClusterNumber() > idx + 2) {
                this.candidates.addAll(
                        this.clustering.getClusterEntityIDs(idx + 1)
                                .stream()
                                .filter(e -> this.worldInfo.getEntity(e) instanceof Building)
                                .filter(e -> !this.worldInfo.getEntityIDsOfType(StandardEntityURN.REFUGE).contains(e))
                                .collect(Collectors.toList()));
            } else if (idx > 0) {
                this.candidates.addAll(
                        this.clustering.getClusterEntityIDs(idx - 1)
                                .stream()
                                .filter(e -> this.worldInfo.getEntity(e) instanceof Building)
                                .filter(e -> !this.worldInfo.getEntityIDsOfType(StandardEntityURN.REFUGE).contains(e))
                                .collect(Collectors.toList()));
            } else {
                // クラスタの中からランダムに選択
                this.candidates.addAll(
                        this.clustering.getClusterEntityIDs(
                                (int) (Math.random() * this.clustering.getClusterNumber()))
                                .stream()
                                .filter(e -> this.worldInfo.getEntity(e) instanceof Building)
                                .filter(e -> !this.worldInfo.getEntityIDsOfType(StandardEntityURN.REFUGE).contains(e))
                                .collect(Collectors.toList()));
            }

        }

        // HELPが聞こえた時
        Collection<Command> commands = this.agentInfo.getHeard();
        for (Command command : commands) {
            // AKSpeak : communication command
            if (!(command instanceof AKSpeak))
                continue;
            AKSpeak speak = (AKSpeak) command;

            // channnel 0 : voice communication
            if (speak.getChannel() != 0)
                continue;

            EntityID sender = speak.getAgentID();
            if (agents.contains(sender))
                continue;

            // 聞こえた範囲の中にある建物をcandidatesに追加
            Collection<EntityID> canHeardEntities = this.worldInfo
                    .getObjectIDsInRange(myID, this.scenarioInfo.getRawConfig().getIntValue("comms.channels.0.range"))
                    .stream()
                    .filter(e -> this.worldInfo.getEntity(e) instanceof Building)
                    .filter(e -> !this.worldInfo.getEntityIDsOfType(StandardEntityURN.REFUGE).contains(e))
                    .filter(e -> this.clustering.getClusterEntityIDs(this.clustering.getClusterIndex(myID)).contains(e))
                    .filter(e -> !this.searched.contains(e))
                    .collect(Collectors.toList());
            this.candidates.addAll(canHeardEntities);
        }

        // ConcurrentModificationExceptionが発生するためコメントアウト
        // for (EntityID En : this.candidates) {
        // if(this.worldInfo.getEntity(En) instanceof Building) {
        // //崩れた建物のみを探索対象とする
        // if (!((Building) this.worldInfo.getEntity(En)).isBrokennessDefined())
        // continue;
        // if (((Building) this.worldInfo.getEntity(En)).getBrokenness() == 0)
        // this.candidates.remove(En);
        //
        // distance = this.worldInfo.getDistance(myID, En);
        // if (distance < nearest) {
        // nearestID = En;
        // nearest = distance;
        // }
        //
        // }
        // }

        this.pathPlanning.setFrom(this.agentInfo.getPosition());
        this.pathPlanning.setDestination(this.candidates);
        List<EntityID> path = this.pathPlanning.calc().getResult();
        // System.out.println("best path is: " + path);
        if (path.size() > 0) {
            this.result = path.get(path.size() - 1);
        } else {
            // candidatesの中からランダムに選択
            int i = 0;
            int rand = (int) (Math.random() * this.candidates.size());
            for (EntityID e : this.candidates) {
                this.result = e;
                if (i == rand)
                    return this;
                i++;
            }
        }
        return this;
    }

    // エージェントの場所が目的地の場所と同じ場合，Trueを返す
    public boolean isReach() {
        EntityID nowPosition = this.agentInfo.getPosition();
        return nowPosition.getValue() == this.result.getValue();
    }

    public boolean isReach(EntityID target) {
        if (this.searched.contains(target))
            return true;
        return this.isReach();
    }

    public void addSearched(EntityID positionId) {
        this.searched.add(positionId);
    }

    // エージェントの場所をsearchedに挿入．
    public void updateSearched() {
        EntityID position = this.agentInfo.getPosition();
        this.searched.add(position);
    }

    // candidatesからエージェントの場所を削除．
    public void updateCandidate() {
        EntityID position = this.agentInfo.getPosition();
        this.candidates.remove(position);
    }

    public void initCandidates() {
        EntityID me = this.agentInfo.getID();
        int idx = this.clustering.getClusterIndex(me);
        Collection<EntityID> ret = this.clustering.getClusterEntityIDs(idx);

        // 建物の位置情報以外の位置情報を取得
        List<EntityID> notBuilding = this.worldInfo.getAllEntities()
                .stream()
                .filter(b -> !(b instanceof Building))
                .map(StandardEntity::getID)
                .collect(Collectors.toList());

        // 避難所の位置情報を取得
        List<EntityID> refuges = this.worldInfo.getEntitiesOfType(StandardEntityURN.REFUGE)
                .stream()
                .map(StandardEntity::getID)
                .collect(Collectors.toList());

        // ret内のnotBuildingの情報を削除
        ret.removeAll(notBuilding);

        // ret内の避難所の情報を削除
        ret.removeAll(refuges);

        this.candidates = new HashSet<>(ret);
        this.searched.clear();

        this.updateSearched();
        this.updateCandidate();

    }

}
