package ch.hsr.servicestoolkit.score.relations;

import java.security.InvalidParameterException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import ch.hsr.servicestoolkit.model.CouplingCriterion;
import ch.hsr.servicestoolkit.model.CouplingType;
import ch.hsr.servicestoolkit.model.Model;
import ch.hsr.servicestoolkit.repository.DataFieldRepository;
import ch.hsr.servicestoolkit.repository.MonoCouplingInstanceRepository;
import ch.hsr.servicestoolkit.solver.SolverConfiguration;

@Component
public class Scorer {
	private final MonoCouplingInstanceRepository monoCouplingInstancesRepo;

	public static final double MAX_SCORE = 10d;
	public static final double MIN_SCORE = -10d;
	public static final double PENALTY_RESPONSIBILITY = -5d;
	public static final double PENALTY_PREDEFINED_SERVICE = MIN_SCORE;

	private Logger log = LoggerFactory.getLogger(Scorer.class);

	private DataFieldRepository nanoentityRepo;

	@Autowired
	public Scorer(final MonoCouplingInstanceRepository repo, final DataFieldRepository nanoentityRepo) {
		this.monoCouplingInstancesRepo = repo;
		this.nanoentityRepo = nanoentityRepo;
	}

	public Map<EntityPair, Map<String, Score>> updateConfig(final Map<EntityPair, Map<String, Score>> scores, final SolverConfiguration config) {
		Map<EntityPair, Map<String, Score>> result = new HashMap<>();
		for (Entry<EntityPair, Map<String, Score>> scoresByFieldTuple : scores.entrySet()) {
			result.put(scoresByFieldTuple.getKey(), new HashMap<>());
			for (Entry<String, Score> scoreByCriterion : scoresByFieldTuple.getValue().entrySet()) {
				result.get(scoresByFieldTuple.getKey()).put(scoreByCriterion.getKey(),
						scoreByCriterion.getValue().withPriority(config.getPriorityForCouplingCriterion(scoreByCriterion.getKey())));
			}
		}
		return result;
	}

	public Map<EntityPair, Map<String, Score>> getScores(final Model model, final SolverConfiguration config) {
		if (new HashSet<>(monoCouplingInstancesRepo.findByModel(model)).isEmpty()) {
			throw new InvalidParameterException("model needs at least 1 coupling criterion in order for gephi clusterer to work");
		}
		Map<EntityPair, Map<String, Score>> result = new HashMap<>();

		addScoresForCharacteristicsCriteria(model, config, result);
		addScoresForConstraintsCriteria(model, config, result);
		addScoresForProximityCriteria(model, config, result);
		return result;

	}

	private void addScoresForProximityCriteria(final Model model, final SolverConfiguration config, final Map<EntityPair, Map<String, Score>> result) {
		Map<EntityPair, Double> lifecycleScores = new CohesiveGroupCriteriaScorer()
				.getScores(monoCouplingInstancesRepo.findByModelAndCriterion(model, CouplingCriterion.IDENTITY_LIFECYCLE));
		addScoresByCriterionToResult(result, CouplingCriterion.IDENTITY_LIFECYCLE, lifecycleScores, config.getPriorityForCouplingCriterion(CouplingCriterion.IDENTITY_LIFECYCLE));

		Map<EntityPair, Double> semanticProximityScores = new SemanticProximityCriterionScorer()
				.getScores(monoCouplingInstancesRepo.findByModelAndCriterion(model, CouplingCriterion.SEMANTIC_PROXIMITY));
		addScoresByCriterionToResult(result, CouplingCriterion.SEMANTIC_PROXIMITY, semanticProximityScores,
				config.getPriorityForCouplingCriterion(CouplingCriterion.SEMANTIC_PROXIMITY));

		Map<EntityPair, Double> responsibilityScores = new ExclusiveGroupCriteriaScorer(PENALTY_RESPONSIBILITY, MAX_SCORE, nanoentityRepo.findAll())
				.getScores(monoCouplingInstancesRepo.findByModelAndCriterion(model, CouplingCriterion.RESPONSIBILITY));
		addScoresByCriterionToResult(result, CouplingCriterion.RESPONSIBILITY, responsibilityScores, config.getPriorityForCouplingCriterion(CouplingCriterion.RESPONSIBILITY));
	}

	private void addScoresForCharacteristicsCriteria(final Model model, final SolverConfiguration config, final Map<EntityPair, Map<String, Score>> result) {
		Map<String, Map<EntityPair, Double>> scoresByCriterion = new CharacteristicsCriteriaScorer()
				.getScores(monoCouplingInstancesRepo.findByModelGroupedByCriterionFilteredByCriterionType(model, CouplingType.COMPATIBILITY));
		for (Entry<String, Map<EntityPair, Double>> distanceScores : scoresByCriterion.entrySet()) {
			addScoresByCriterionToResult(result, distanceScores.getKey(), distanceScores.getValue(), config.getPriorityForCouplingCriterion(distanceScores.getKey()));
		}
	}

	private void addScoresForConstraintsCriteria(final Model model, final SolverConfiguration config, final Map<EntityPair, Map<String, Score>> result) {
		Map<EntityPair, Double> securityScores = new SeparationCriteriaScorer()
				.getScores(monoCouplingInstancesRepo.findByModelAndCriterion(model, CouplingCriterion.SECURITY_CONSTRAINT));
		addScoresByCriterionToResult(result, CouplingCriterion.SECURITY_CONSTRAINT, securityScores, config.getPriorityForCouplingCriterion(CouplingCriterion.SECURITY_CONSTRAINT));

		Map<EntityPair, Double> predefinedServiceScores = new ExclusiveGroupCriteriaScorer(PENALTY_PREDEFINED_SERVICE, MAX_SCORE, nanoentityRepo.findAll())
				.getScores(monoCouplingInstancesRepo.findByModelAndCriterion(model, CouplingCriterion.PREDEFINED_SERVICE));
		addScoresByCriterionToResult(result, CouplingCriterion.PREDEFINED_SERVICE, predefinedServiceScores,
				config.getPriorityForCouplingCriterion(CouplingCriterion.PREDEFINED_SERVICE));
	}

	private void addScoresByCriterionToResult(final Map<EntityPair, Map<String, Score>> result, final String couplingCriterionName, final Map<EntityPair, Double> scores,
			final Double priority) {
		for (Entry<EntityPair, Double> fieldScore : scores.entrySet()) {
			addScoresToResult(result, fieldScore.getKey(), couplingCriterionName, fieldScore.getValue(), priority);
		}
	}

	private void addScoresToResult(final Map<EntityPair, Map<String, Score>> result, final EntityPair fields, final String criterionName, final double score,
			final double priority) {
		if (fields.nanoentityA.getId().equals(fields.nanoentityB.getId())) {
			log.warn("score on same field ignored. Field: {}, Score: {}, Criterion: {}", fields.nanoentityA, score, criterionName);
			return;
		}

		if (result.get(fields) == null) {
			result.put(fields, new HashMap<>());
		}
		result.get(fields).put(criterionName, new Score(score, priority));
	}

}

@@ -3612,36 +3612,45 @@
 oentityRepo.find
-All(
+ByModel(model
 ))%0A%09%09%09%09.getScore

fix exclusivegroupScorer, take modelId into account