package autumn_2023.module.complex;

import static rescuecore2.standard.entities.StandardEntityURN.AMBULANCE_TEAM;
import static rescuecore2.standard.entities.StandardEntityURN.CIVILIAN;
import static rescuecore2.standard.entities.StandardEntityURN.REFUGE;
import adf.core.agent.communication.MessageManager;
import adf.core.agent.communication.standard.bundle.MessageUtil;
import adf.core.agent.communication.standard.bundle.StandardMessagePriority;
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.communication.CommunicationMessage;
import adf.core.component.module.algorithm.Clustering;
import adf.core.component.module.algorithm.PathPlanning;
import adf.core.component.module.complex.HumanDetector;
import adf.core.debug.DefaultLogger;
import autumn_2023.module.comm.infomation.MessageCost;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;

import rescuecore2.standard.entities.AmbulanceCentre;
import rescuecore2.standard.entities.Civilian;
import rescuecore2.standard.entities.Human;
import rescuecore2.standard.entities.StandardEntity;
import rescuecore2.standard.entities.StandardEntityURN;
import rescuecore2.worldmodel.EntityID;

public class SampleHumanDetector extends HumanDetector {

  private Clustering clustering;

  private EntityID result;

  private Logger logger;

  // 救急司令所からの埋没市民のメッセージリスト
  private List<EntityID> receivedCentreCivilians = new ArrayList<>();
  // コスト計算用パスプランニング
  private PathPlanning pathplanning;

  public SampleHumanDetector(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) {
    super(ai, wi, si, moduleManager, developData);
    logger = DefaultLogger.getLogger(agentInfo.me());
    this.clustering = moduleManager.getModule("SampleHumanDetector.Clustering",
        "adf.impl.module.algorithm.KMeansClustering");
    // 追加
    this.pathplanning = moduleManager.getModule("SampleHumanDetector.PathPlanning", "adf.impl.module.algorithm.DijkstraPathPlanning");

    registerModule(this.clustering);
  }


  @Override
  public HumanDetector updateInfo(MessageManager messageManager) {
    logger.debug("Time:" + agentInfo.getTime());
    super.updateInfo(messageManager);
    this.pathplanning.updateInfo(messageManager);

    // そのステップで受信した(前ステップで送信された)メッセージ一覧
    List<CommunicationMessage> messages = messageManager.getReceivedMessageList();
    for(CommunicationMessage ms : messages) 
    {
      // msが市民の情報であれば
      if (ms instanceof MessageCivilian) 
      {
        MessageCivilian msCiv = (MessageCivilian) ms;
        MessageUtil.reflectMessage(this.worldInfo, msCiv);
        if (this.worldInfo.getEntity(msCiv.getSenderID()) instanceof AmbulanceCentre) 
        {
          if(!receivedCentreCivilians.contains(msCiv.getAgentID()))
          {
            receivedCentreCivilians.add(msCiv.getAgentID());
          }
        }
      }
    }
    for(EntityID receive : receivedCentreCivilians) 
    {
      int taskcost = CalculationCost(receive);
      // 新規メッセージクラスを用いて救急司令所に送信
      CommunicationMessage costmes = new MessageCost(true, StandardMessagePriority.HIGH, receive, taskcost);
      messageManager.addMessage(costmes);
    }

    // このステップ内で知覚したエンティティのうち，救助対象の市民リストを作成
    Set<EntityID> changed = this.worldInfo.getChanged().getChangedEntities();
    List<EntityID> changedRescueTargets = filterRescueTargets((Collection)changed);
    // 司令所に送るメッセージを作成・送信（現状：救急司令所のみ）
    // 市民の情報を送信
    for(EntityID change : changedRescueTargets) 
    {
      Civilian changeCivilian = (Civilian)this.worldInfo.getEntity(change);
      CommunicationMessage message = new MessageCivilian(true, StandardMessagePriority.HIGH, changeCivilian);
      messageManager.addMessage(message);
    }

    return this;
  }


  @Override
  public HumanDetector calc() {
    Human transportHuman = this.agentInfo.someoneOnBoard();
    if (transportHuman != null) {
      logger.debug("someoneOnBoard:" + transportHuman);
      this.result = transportHuman.getID();
      return this;
    }
    if (this.result != null) {
      Human target = (Human) this.worldInfo.getEntity(this.result);
      if (!isValidHuman(target)) {
        logger.debug("Invalid Human:" + target + " ==>reset target");
        this.result = null;
      }
    }
    if (this.result == null) {
      this.result = calcTarget();
    }
    return this;
  }


  private EntityID calcTarget() {
    List<Human> rescueTargets = filterRescueTargets(
        this.worldInfo.getEntitiesOfType(CIVILIAN));
    List<Human> rescueTargetsInCluster = filterInCluster(rescueTargets);
    List<Human> targets = rescueTargetsInCluster;
    if (targets.isEmpty())
      targets = rescueTargets;

    logger.debug("Targets:" + targets);
    if (!targets.isEmpty()) {
      targets.sort(new DistanceSorter(this.worldInfo, this.agentInfo.me()));
      Human selected = targets.get(0);
      logger.debug("Selected:" + selected);
      return selected.getID();
    }

    return null;
  }


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


  private List<Human>
      filterRescueTargets(Collection<? extends StandardEntity> list) {
    List<Human> rescueTargets = new ArrayList<>();
    for (StandardEntity next : list) {
      if (!(next instanceof Human))
        continue;
      Human h = (Human) next;
      if (!isValidHuman(h))
        continue;
      if (h.getBuriedness() == 0)
        continue;
      rescueTargets.add(h);
    }
    return rescueTargets;
  }


  private List<Human>
      filterInCluster(Collection<? extends StandardEntity> entities) {
    int clusterIndex = clustering.getClusterIndex(this.agentInfo.getID());
    List<Human> filter = new ArrayList<>();
    HashSet<StandardEntity> inCluster = new HashSet<>(
        clustering.getClusterEntities(clusterIndex));
    for (StandardEntity next : entities) {
      if (!(next instanceof Human))
        continue;
      Human h = (Human) next;
      if (!h.isPositionDefined())
        continue;
      StandardEntity position = this.worldInfo.getPosition(h);
      if (position == null)
        continue;
      if (!inCluster.contains(position))
        continue;
      filter.add(h);
    }
    return filter;

  }

  private class DistanceSorter implements Comparator<StandardEntity> {

    private StandardEntity reference;
    private WorldInfo worldInfo;

    DistanceSorter(WorldInfo wi, StandardEntity reference) {
      this.reference = reference;
      this.worldInfo = wi;
    }


    public int compare(StandardEntity a, StandardEntity b) {
      int d1 = this.worldInfo.getDistance(this.reference, a);
      int d2 = this.worldInfo.getDistance(this.reference, b);
      return d1 - d2;
    }
  }

  private boolean isValidHuman(StandardEntity entity) {
    if (entity == null)
      return false;
    if (!(entity instanceof Human))
      return false;

    Human target = (Human) entity;
    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();
    if (positionURN == REFUGE || positionURN == AMBULANCE_TEAM)
      return false;

    return true;
  }

  // cost計算
  private int CalculationCost(EntityID target){
    double targetDistance = this.pathplanning.getDistance(this.agentInfo.getID(), target);
    return (int)(Math.ceil(targetDistance / 40000));
  }
}