package bdi4jade.extension.remediation.reasoning;

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

import bdi4jade.core.Capability;
import bdi4jade.extension.remediation.PlanRequiredResource;
import bdi4jade.extension.remediation.Resource;
import bdi4jade.extension.remediation.ResourcePreferences;
import bdi4jade.extension.remediation.goal.ConstrainedGoal;
import bdi4jade.extension.remediation.goal.ObjectiveFunction;
import bdi4jade.extension.remediation.logics.And;
import bdi4jade.extension.remediation.logics.LogicalExpression;
import bdi4jade.goal.Goal;
import bdi4jade.plan.Plan;
import bdi4jade.reasoning.DefaultPlanSelectionStrategy;
import bdi4jade.reasoning.PlanSelectionStrategy;

public class RemediationPlanSelectionStrategy extends DefaultPlanSelectionStrategy implements PlanSelectionStrategy {

	private class Boundary {
		public final Double max;
		public final Double min;

		public Boundary(Double min, Double max) {
			this.min = min;
			this.max = max;
		}
	}

	public RemediationPlanSelectionStrategy(Capability capability) {
		setCapability(capability);
	}

	private Map<Resource, Boundary> getBoundaries(Set<Resource> resources, Set<Plan> plans) {
		Map<Resource, Boundary> boundaries = new HashMap<>();

		for (Resource resource : resources) {
			boundaries.put(resource, new Boundary(getMinBoundary(resource, plans), getMaxBoundary(resource, plans)));
		}
		return boundaries;
	}

	private Set<Plan> getCandidatePlans(ConstrainedGoal goal, Set<Resource> resources, Set<Plan> plans) {
		Set<Plan> candidatePlans = new HashSet<>();

		LogicalExpression constraint = null;
		for (LogicalExpression c : goal.getOperationConstraints()) {
			if (constraint == null) {
				constraint = c;
			} else {
				c = new And(c, constraint);
			}
		}

		for (Plan plan : plans) {
			PlanRequiredResource prr = (PlanRequiredResource) plan.getMetadata(PlanRequiredResource.METADATA_NAME);
			if (prr == null) {
				candidatePlans.add(plan);
			} else {
				// FIXME haven't tested
				Map<Object, Object> values = new HashMap<>();
				values.putAll(prr.getRequiredResources());
				if (constraint != null && constraint.getValue(values)) {
					candidatePlans.add(plan);
				}
			}
		}

		return candidatePlans;
	}

	private Double getMaxBoundary(Resource resource, Set<Plan> plans) {
		Double max = null;
		for (Plan plan : plans) {
			PlanRequiredResource prr = (PlanRequiredResource) plan.getMetadata(PlanRequiredResource.METADATA_NAME);
			if (prr == null) {
				continue;
			}
			Double requiredResource = prr.getRequiredResource(resource);
			if (requiredResource != null && (max == null || max < requiredResource)) {
				max = requiredResource;
			}
		}
		return max;
	}

	private Double getMinBoundary(Resource resource, Set<Plan> plans) {
		Double min = null;
		for (Plan plan : plans) {
			PlanRequiredResource prr = (PlanRequiredResource) plan.getMetadata(PlanRequiredResource.METADATA_NAME);
			if (prr == null) {
				continue;
			}
			Double requiredResource = prr.getRequiredResource(resource);
			if (requiredResource != null && (min == null || min > requiredResource)) {
				min = requiredResource;
			}
		}
		return min;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Plan selectPlan(Goal goal, Set<Plan> capabilityPlans) {
		if (goal instanceof ConstrainedGoal) {
			ConstrainedGoal g_c = (ConstrainedGoal) goal;
			ResourcePreferences preferences = (ResourcePreferences) capability.getBeliefBase()
					.getBelief(ResourcePreferences.NAME);
			Map<Resource, Boundary> boundaries = getBoundaries(preferences.getResources(), capabilityPlans);

			Set<Plan> candidatePlans = getCandidatePlans(g_c, preferences.getResources(), capabilityPlans);

			Plan selectedPlan = null;
			Double maxUtility = null;
			for (Plan plan : candidatePlans) {
				double utility = 0;

				for (Resource resource : preferences.getResources()) {
					ObjectiveFunction obj = g_c.getObjectiveFunction().get(resource);
					if (obj == null) {
						continue;
					}

					Double prr = ((PlanRequiredResource) plan.getMetadata(PlanRequiredResource.METADATA_NAME))
							.getRequiredResource(resource);
					Double preference = preferences.getPreferenceForResource(resource);
					Boundary boundary = boundaries.get(resource);

					if (preference != null) {
						double value = (prr - boundary.min) / ((boundary.max - boundary.min));
						if (obj.equals(ObjectiveFunction.MINIMIZE)) {
							value = 1 - value;
						}
						utility += preference * value;
					}

					if (selectedPlan == null || maxUtility < utility) {
						selectedPlan = plan;
						maxUtility = utility;
					}
				}
			}
			return selectedPlan;
		} else {
			return super.selectPlan(goal, capabilityPlans);
		}
	}

}
