package AITB_2022.module.complex;

import adf.core.agent.info.*;
import adf.core.component.module.complex.Search;
import adf.core.component.module.algorithm.Clustering;
import adf.core.agent.module.ModuleManager;
import adf.core.agent.communication.MessageManager;
import adf.core.agent.develop.DevelopData;
import adf.core.component.module.algorithm.PathPlanning;

import rescuecore2.standard.entities.*;
import rescuecore2.worldmodel.EntityID;
import rescuecore2.standard.messages.AKSpeak;
import rescuecore2.messages.Command;

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

import javax.swing.text.html.parser.Entity;

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

public class AITPoliceSearch extends Search {
    private EntityID result;
    private Clustering clustering;
    // HashSetは，要素の重複を許可しない集合構造を表す．また，順番も持ちません．
    private Set<EntityID> candidates = new HashSet<>();
    private Set<EntityID> searched = new HashSet<>();
    private Set<EntityID> agents = new HashSet<>();


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

    public AITPoliceSearch(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);

        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;

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

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

        return this;
    }

    @Override
    public Search calc() {
        if (this.result != null && !this.isReach()) {
            return this;
        }
        //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(this.agentInfo.getID(),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(this.agentInfo.getID())).contains(e))
                    .filter(e -> !this.searched.contains(e))
                    .collect(Collectors.toList());
            this.candidates.addAll(canHeardEntities);
        }


        this.result = this.candidates
                .stream()
                .min(Comparator.comparing(i -> this.getpathDistance(this.agentInfo.getPosition(), i))) // pathPlanningで距離を取る
                .orElse(null);

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

    }

    private int getpathDistance(EntityID sourceID, EntityID destinationID) {
        PathPlanning pathPlanning = this.moduleManager.getModule(
                "SampleRoadDetector.PathPlanning",
                "adf.impl.module.algorithm.DijkstraPathPlanning");
        // pathplanningでEntityIDをListで取得
        List<EntityID> path = pathPlanning.setFrom(sourceID).setDestination(destinationID).calc().getResult();
        List<Area> areas = new ArrayList<>();

        // pathplanningで得たEntityIDを一つずつAreaにキャストする
        for (EntityID id : path) {
            // EntityIDからStandardEntityを取得
            StandardEntity entity = this.worldInfo.getEntity(id);
            // entityがArea型でなければ以下の処理をしない
            if (!(entity instanceof Area))
                continue;
            // entityをArea型にキャスト
            Area area = (Area) entity;
            // Area型のリストareasにareaを入れる
            areas.add(area);
        }
        double x = 0;
        double x_pre = 0;
        double y = 0;
        double y_pre = 0;
        double dis_d = 0;
        for (Area area : areas) {
            // 最初の座標は計算をしない
            if (x_pre != 0 && y_pre != 0) {
                // Areaの中心座標を取る
                x = area.getX() - x_pre;
                y = area.getY() - y_pre;
                // 中心座標同士の距離を取る
                dis_d += Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
            }
            // 中心座標の値を保持
            x_pre = area.getX();
            y_pre = area.getY();
        }
        // 距離をカウント数にキャスト
        int dis = (int) dis_d / 30000;
        return dis;
    }
}
