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.action.Action;
import adf.core.agent.communication.MessageManager;
import adf.core.agent.communication.standard.bundle.StandardMessageBundle;
import adf.core.agent.communication.standard.bundle.StandardMessagePriority;
import adf.core.agent.communication.standard.bundle.information.MessageBuilding;
import adf.core.agent.communication.standard.bundle.information.MessageCivilian;
import adf.core.agent.communication.standard.bundle.information.MessageFireBrigade;
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.complex.HumanDetector;
import adf.core.debug.DefaultLogger;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.log4j.Logger;

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

public class CentralizedControlFBHumanDetector extends HumanDetector {

  private Clustering clustering;

  private EntityID result;

  private Logger logger;

  private MessageManager mm;

  public CentralizedControlFBHumanDetector(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");
    registerModule(this.clustering);
  }


  /**
   * 新しく受信したメッセージから情報を取得するためのメソッド
   * 
   * 現在のステップで知覚した埋没市民の情報を送信する
   * ※ここに消防隊の情報を送信する記述はないが，Tacticsの方で常に送信しているので記述しなくてもいい
   */
  @Override
  public HumanDetector updateInfo(MessageManager messageManager) {
    logger.debug("Time:" + agentInfo.getTime());
    super.updateInfo(messageManager);
    this.mm = messageManager;

    Set<EntityID> changes = this.worldInfo.getChanged().getChangedEntities();
    for(EntityID id : changes){
      StandardEntity entity = this.worldInfo.getEntity(id);
      if(!(entity instanceof Civilian)) continue;
      Civilian civ = (Civilian) entity;

      if(!this.isNeedRescueHuman(civ)){
        continue;
      }
      StandardMessagePriority level = StandardMessagePriority.NORMAL;
      MessageCivilian civ_mes = new MessageCivilian(true, level, civ);
      
      messageManager.addMessage(civ_mes,true);
    }

    return this;
  }

  /**
   * 救助対象のIDを求めるメソッド
   * 
   * 司令所の司令で行動するため，救助対象を決定しないようにしている
   */
  @Override
  public HumanDetector calc() {
    if(this.agentInfo.getTime() < scenarioInfo.getKernelAgentsIgnoreuntil()) return this;

    Set<EntityID> mescivs = this.mm.getReceivedMessageList(MessageCivilian.class).stream()
    .map(MessageCivilian.class::cast)
    .map(MessageCivilian::getAgentID)
    .collect(Collectors.toSet());
    
    Set<EntityID> changecivs = this.agentInfo.getChanged().getChangedEntities().stream()
    .map(this.worldInfo::getEntity)
    .filter(e -> (e instanceof Civilian))
    .map(Civilian.class::cast)
    .filter(Civilian::isBuriednessDefined)
    .filter(e -> e.getBuriedness() != 0)
    .map(Civilian::getID)
    .collect(Collectors.toSet());

    changecivs.removeAll(mescivs);

    EntityID myid = this.agentInfo.getID();
    Optional<EntityID> minID = changecivs.stream()
    .min((e1,e2) -> {
      return(this.worldInfo.getDistance(myid, e1) - this.worldInfo.getDistance(myid, e2));
    });

    if(minID.isEmpty()) return this;

    this.result = minID.get();;

    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;
  }

  /**
   * 救助対象IDを返すメソッド
   */
  @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;
  }

  private boolean isNeedRescueHuman(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.isBuriednessDefined() || target.getBuriedness() == 0)
      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;
  }
}