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.communication.standard.bundle.information.MessageCivilian;
import adf.core.agent.develop.DevelopData;

import rescuecore2.standard.entities.*;
import rescuecore2.worldmodel.EntityID;

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

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

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

import adf.core.component.communication.*;

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

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

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

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

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

        // エージェントが消防隊である場合
        if (this.agentInfo.me().getStandardURN() == FIRE_BRIGADE) {
            // エージェントと同じ建物内にいる市民について
            ArrayList<StandardEntity> civilians = new ArrayList<StandardEntity>(
                    this.worldInfo.getEntitiesOfType(CIVILIAN));
            civilians.removeIf(e -> !(((Civilian) e).getPosition().equals(this.agentInfo.getPosition())) ||
                    sentBuriedCivilianIDs.contains(e.getID()) ||
                    this.worldInfo.getEntity(((Civilian) e).getPosition()).getStandardURN() != BUILDING);
            for (StandardEntity entity : civilians) {
                Civilian civilian = (Civilian) entity;
                // その市民が埋没度していなければ
                if (civilian.isBuriednessDefined() && civilian.getBuriedness() <= 0) {
                    // 通信によってその市民の情報を伝播する
                    CommunicationMessage message = new MessageCivilian(true, civilian);
                    mm.addMessage(message);
                    // System.out.println("A message of" + civilian.getID().toString() + "is
                    // transported");
                    // 情報が送信された市民のIDを記憶する
                    this.sentBuriedCivilianIDs.add(civilian.getID());
                }
            }
        }

        return this;
    }

    @Override
    public Search calc() {
        EntityID myID = this.agentInfo.getID();
        EntityID nearestID = null;
        int dis_time, nearest = 1000;

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

        for (EntityID En : this.candidates) {
            if (this.searched.contains(En)) {
                this.candidates.remove(En);
                continue;
            }
            double distance = this.worldInfo.getDistance(myID, En);
            dis_time = (int) (distance / 7000);
            if (dis_time < nearest) {
                nearestID = En;
                nearest = dis_time;
            }
        }
        this.result = nearestID;
        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();

    }

}
