package bdi4jade.extension.remediation.reasoning;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import bdi4jade.belief.PredicateBelief;
import bdi4jade.core.Capability;
import bdi4jade.core.GoalUpdateSet;
import bdi4jade.core.GoalUpdateSet.GoalDescription;
import bdi4jade.core.Intention;
import bdi4jade.event.GoalEvent;
import bdi4jade.event.GoalListener;
import bdi4jade.extension.remediation.goal.CauseEffectProblem;
import bdi4jade.extension.remediation.goal.CauseEffectProblem.CauseEffectProblemStatus;
import bdi4jade.extension.remediation.goal.CauseFactorStatus;
import bdi4jade.extension.remediation.graph.AlternativeCauseSet;
import bdi4jade.extension.remediation.graph.CauseEffectKnowledgeModel;
import bdi4jade.extension.remediation.graph.CauseEffectRelationship;
import bdi4jade.extension.remediation.logics.Fact;
import bdi4jade.extension.remediation.logics.Predicate;
import bdi4jade.extension.undo.GoalAchievementMetadata;
import bdi4jade.extension.undo.RevertingCapability;
import bdi4jade.extension.undo.reasoning.RevertingOptionGenerationFunction;
import bdi4jade.goal.Goal;
import bdi4jade.goal.GoalStatus;
import bdi4jade.goal.NestedGoal;
import bdi4jade.goal.PredicateGoal;

public class RemediationOptionGenerationFunction extends RevertingOptionGenerationFunction {

	private Capability capability;
	private final Map<Goal, CauseEffectProblem> causeEffectProblems;

	public RemediationOptionGenerationFunction(Capability capability) {
		super(capability);
		this.causeEffectProblems = new HashMap<>();
	}

	private CauseEffectKnowledgeModel getCauseEffectKnowledgeModel() {
		return (CauseEffectKnowledgeModel) this.capability.getBeliefBase().getBelief(CauseEffectKnowledgeModel.NAME);
	}

	private Boolean causeFinished(Collection<CauseFactorStatus> causeEffectStatus) {
		for (CauseFactorStatus causeFactor : causeEffectStatus) {
			if (causeFactor.getUpdatedStatus() == null || !causeFactor.getUpdatedStatus()) {
				return Boolean.FALSE;
			}
		}
		return Boolean.TRUE;
	}

	private Boolean causeNotFound(Collection<CauseFactorStatus> causeEffectStatus) {
		boolean existUnachievable = false;
		boolean existOnGoing = false;
		for (CauseFactorStatus causeFactor : causeEffectStatus) {
			if (causeFactor.getInitialStatus() == null) {
				if (causeFactor.getTestGoal() != null) {
					if (causeFactor.getTestGoalStatus() != null) {
						existUnachievable = true;
					} else {
						existOnGoing = true;
					}
				}
			}
		}
		return existUnachievable && !existOnGoing;
	}

	@Override
	public void generateGoals(GoalUpdateSet goalUpdateSet) {
		// Algorithm 1 - lines 2-5
		for (GoalDescription goalDescription : goalUpdateSet.getCurrentGoals()) {
			Goal goal = goalDescription.getGoal();
			Goal parentGoal = null;
			if (goal instanceof NestedGoal) {
				parentGoal = goal;
				goal = ((NestedGoal) goal).getGoal();
			}

			CauseEffectProblem cep = this.causeEffectProblems.get(goal);
			CauseEffectRelationship cer = getCauseEffectKnowledgeModel().getCauseEffectRelationship(goal);
			if (cep == null && cer != null) {
				cep = new CauseEffectProblem((PredicateGoal<?>) goal, cer, capability.getMyAgent(), parentGoal);
				causeEffectProblems.put(cep.getEffectGoal(), cep);

				// Here a new goal achievement metadata is created
				if (this.capability instanceof RevertingCapability) {
					for (Intention intention : this.capability.getMyAgent().getIntentions()) {
						if (intention.getGoal().equals(goal)) {
							((RevertingCapability) this.capability)
									.createGoalAchievementMetadata((PredicateGoal<?>) goal, intention);
						}
					}
				}
			}

		}

		Collection<CauseEffectProblem> problems = new ArrayList<>(causeEffectProblems.values());
		for (CauseEffectProblem cep : problems) {
			// Algorithm 1 - lines 7-17
			for (CauseFactorStatus causeFactor : cep.getCauseFactorStatus()) {
				updateCauseFactor(causeFactor);
			}

			// Algorithm 1 - lines 18-35
			if (cep.getEffectGoalStatus() != null) {
				if (knownCause(cep, cep.getCauseEffectRelationship())) {

					// Reversion trigger is updated with current cause factor predicates
					if (this.capability instanceof RevertingCapability) {
						updateReversionTrigger(cep);
					}

					if (causeFinished(cep.getCauseFactorStatus())) {
						setEndState(cep);
						causeEffectProblems.remove(cep.getEffectGoal());
					} else {
						for (CauseFactorStatus causeFactor : cep.getCauseFactorStatus()) {
							if (!causeFactor.getUpdatedStatus() && causeFactor.getAchievementGoal() == null) {
								Goal achievementGoal = causeFactor.generateAchievementGoal();
								GoalListener listener = new GoalListener() {
									@Override
									public void goalPerformed(GoalEvent event) {
										if (event.getStatus().isFinished()) {
											causeFactor.setAchievementGoalStatus(event.getStatus());
										}
									}
								};
								goalUpdateSet.generateGoal(achievementGoal, capability, listener);
							}
						}
					}
				} else if (causeNotFound(cep.getCauseFactorStatus())) {
					setEndState(cep);
					causeEffectProblems.remove(cep.getEffectGoal());
				} else {
					for (CauseFactorStatus causeFactor : cep.getCauseFactorStatus()) {
						if (causeFactor.getInitialStatus() == null && causeFactor.getTestGoal() == null) {
							Goal testGoal = causeFactor.generateTestGoal();
							GoalListener listener = new GoalListener() {
								@Override
								public void goalPerformed(GoalEvent event) {
									if (event.getStatus().isFinished()) {
										causeFactor.setTestGoalStatus(event.getStatus());
									}
								}
							};
							goalUpdateSet.generateGoal(testGoal, capability, listener);
						}
					}
				}
			}

		}
		super.generateGoals(goalUpdateSet);
	}

	private Boolean knownCause(CauseEffectProblem cep, CauseEffectRelationship cer) {
		for (CauseFactorStatus cf : cep.getCauseFactorStatus()) {
			if (cf.getInitialStatus() == null) {
				return Boolean.FALSE;
			}
		}

		Set<CauseFactorStatus> currentCauses = new HashSet<>();

		for (Fact fact : cer.getMandatoryCauses()) {
			CauseFactorStatus cf = cep.getCauseFactorStatus(fact);
			if (!cf.getInitialStatus()) {
				return Boolean.FALSE;
			} else {
				currentCauses.add(cf);
			}
		}

		for (Fact fact : cer.getOptionalCauses()) {
			CauseFactorStatus cf = cep.getCauseFactorStatus(fact);
			if (cf.getInitialStatus()) {
				currentCauses.add(cf);
			}
		}

		for (AlternativeCauseSet acs : cer.getAlternativeCauseSets()) {
			Set<CauseFactorStatus> alternativeCauses = new HashSet<>();
			for (Fact fact : acs.getAlternativeCauses()) {
				CauseFactorStatus cf = cep.getCauseFactorStatus(fact);
				if (cf.getInitialStatus()) {
					alternativeCauses.add(cf);
					currentCauses.add(cf);
				}
				if (alternativeCauses.size() < acs.getMin() || alternativeCauses.size() > acs.getMax()) {
					return Boolean.FALSE;
				}
			}
		}

		cep.setCauseFactorStatus(currentCauses);
		return Boolean.TRUE;
	}

	@Override
	public void setCapability(Capability capability) {
		if (this.capability != null) {
			if (!this.capability.equals(capability)) {
				throw new IllegalArgumentException(
						"This reasoning strategy is already associated with another capability.");
			}
		}
		this.capability = capability;
	}

	private void setEndState(CauseEffectProblem cep) {
		Collection<CauseFactorStatus> causeEffectStatus = cep.getCauseFactorStatus();
		GoalStatus effectStatus = cep.getEffectGoalStatus();

		boolean existsResolved = false;
		boolean existsUnresolved = false;
		for (CauseFactorStatus causeFactor : causeEffectStatus) {
			if (causeFactor.getUpdatedStatus()) {
				existsResolved = true;
			} else {
				existsUnresolved = true;
			}
		}

		if (effectStatus.equals(GoalStatus.ACHIEVED)) {
			if (!existsResolved) {
				cep.setStatus(CauseEffectProblemStatus.MITIGATED);
			} else if (!existsUnresolved) {
				cep.setStatus(CauseEffectProblemStatus.FULLY_RESOLVED);
			} else {
				cep.setStatus(CauseEffectProblemStatus.PARTIALLY_RESOLVED);
			}
		} else {
			if (!existsResolved) {
				cep.setStatus(CauseEffectProblemStatus.UNSUCCESSFUL);
			} else if (!existsUnresolved) {
				cep.setStatus(CauseEffectProblemStatus.CAUSE_RESOLVED);
			} else {
				cep.setStatus(CauseEffectProblemStatus.CAUSE_PARTIALLY_RESOLVED);
			}
		}
		System.out.println(cep);
	}

	private void updateCauseFactor(CauseFactorStatus cf) {
		PredicateBelief<?> belief = (PredicateBelief<?>) this.capability.getBeliefBase()
				.getBelief(cf.getFact().getPredicate());
		if (belief == null)
			return;

		// Algorithm 1 - lines 8-12
		if (cf.getInitialStatus() == null) {
			if (belief.getValue().equals(cf.getFact().getValue())) {
				cf.setInitialStatus(Boolean.TRUE);
			} else {
				cf.setInitialStatus(Boolean.FALSE);
			}
			cf.setUpdatedStatus(Boolean.FALSE);
			// Algorithm 1 - lines 13-17
		} else {
			if ((cf.getInitialStatus() && belief.getValue().equals(!cf.getFact().getValue()))
					|| (!cf.getInitialStatus() && belief.getValue().equals(cf.getFact().getValue()))) {
				cf.setUpdatedStatus(Boolean.TRUE);
			}
		}
	}

	private void updateReversionTrigger(CauseEffectProblem cep) {
		ArrayList<Predicate> reversionTrigger = new ArrayList<>();
		for (CauseFactorStatus causeFactor : cep.getCauseFactorStatus()) {
			reversionTrigger.add(causeFactor.getFact().getPredicate());
		}
		GoalAchievementMetadata gam = ((RevertingCapability) this.capability).getGoalAchievementMetadata()
				.get(cep.getEffectGoal());
		gam.setReversionTrigger(reversionTrigger);
	}

}
