package no.valg.valgnatt.backend.mandatberegning.beregning;

import no.valg.valgnatt.backend.mandatberegning.domene.PartiKvotient;
import no.valg.valgnatt.database.domene.OpptaltStatus;
import no.valg.valgnatt.database.geografi.Kommune;
import no.valg.valgnatt.database.geografi.Område;
import no.valg.valgnatt.database.geografi.PartiMandater;

import java.math.BigDecimal;
import java.util.Map;
import java.util.TreeMap;

import static no.valg.valgnatt.backend.mandatberegning.beregning.MandatberegningFelles.begrensAntallKvotienter;
import static no.valg.valgnatt.backend.mandatberegning.beregning.MandatberegningFelles.sorterteKvotienter;

/**
 * Distriktsmandatkalkulasjon tilbyr den vanlige (distrikts)mandatberegningen som utføres i alle kommuner, fylker og samevalgdistrikter.
 * Utregningen og fordelingen utføres etter Sainte-Laguës modifiserte metode (https://no.wikipedia.org/wiki/Sainte-Lagu%C3%ABs_metode).
 * I tillegg regnes det ut endel andre tall som sier noe om hvor nær man er til å vinne/tape sistemandatet. Datamodellen/geografien
 * oppdateres med data og det returnes et objekt med resultatene.
 */
public class Distriktsmandat {

	private Distriktsmandat() {
	}

	public static DistriktsmandatResultat beregnOgOppdater(Område område, int antallMandater, boolean stortingsDistriktsmandaterIFYValg, boolean prognose) {
		boolean listeStemmer = område.getClass() == Kommune.class && område.getLavesteOS() == OpptaltStatus.OPPGJØR;
		Map<String, Integer> stemmerPrParti = område.getValgdistrikt().getStemmerPrPartiTilMandatberegning(listeStemmer, true, prognose);

		PartiKvotient[] alleKvotienter = sorterteKvotienter(antallMandater, stemmerPrParti, null);
		PartiKvotient[] kvotienterMedMandat = begrensAntallKvotienter(alleKvotienter, antallMandater);

		Map<String, Integer> mandaterVunnetPrParti = mandatFordeling(kvotienterMedMandat);
		Map<String, BigDecimal> sisteKvotientPrParti = MandatberegningFelles.lavesteKvotienterMedMandat(antallMandater, kvotienterMedMandat);
		Map<String, BigDecimal> nesteKvotientPrParti = MandatberegningFelles.høyesteKvotienterUtenMandat(antallMandater, alleKvotienter);
		Map<String, Integer> lavesteMandatrangPrParti = MandatberegningFelles.lavesteMandatrangeringerMedMandat(kvotienterMedMandat);
		Map<String, Integer> høyesteMandatrangPrParti = MandatberegningFelles.høyesteMandatrangeringerUtenMandat(antallMandater, alleKvotienter);


		for (String partikode : stemmerPrParti.keySet()) {
			PartiMandater pm = område.getValgdistrikt().getPartiMandater(partikode);
			int antallMandaterVunnet = mandaterVunnetPrParti.get(partikode) == null ? 0 : mandaterVunnetPrParti.get(partikode);
			if (stortingsDistriktsmandaterIFYValg) {
				pm.getMandatData(prognose).setAntallDistriktsmandaterGittSTValg(antallMandaterVunnet);
			} else {
				pm.getMandatData(prognose).getDistriktsmandater().setAntall(antallMandaterVunnet);
				pm.getMandatData(prognose).getDistriktsmandater().setLavesteKvotientMedMandat(sisteKvotientPrParti.getOrDefault(partikode, BigDecimal.ZERO));
				pm.getMandatData(prognose).getDistriktsmandater().setHøyesteKvotientUtenMandat(nesteKvotientPrParti.getOrDefault(partikode, BigDecimal.ZERO));
				pm.getMandatData(prognose).getDistriktsmandater().setLavesteMandatrangMedMandat(lavesteMandatrangPrParti.getOrDefault(partikode, 0));
				pm.getMandatData(prognose).getDistriktsmandater().setHøyesteMandatrangUtenMandat(høyesteMandatrangPrParti.getOrDefault(partikode, 0));
			}
		}
		return new DistriktsmandatResultat(mandaterVunnetPrParti, sisteKvotientPrParti, lavesteMandatrangPrParti, nesteKvotientPrParti, høyesteMandatrangPrParti);
	}

	/**
	 * Brukt til å fordele mandater til et fylkesting/kommunestyrer basert på antall mandater
	 * og stemmer pr parti. Ingenting fancy med utjevningsmandater, bare en "rett-frem-
	 * fordeling" etter prinsippet fra Sainte-Legües modifiserte metode.
	 */
	private static Map<String, Integer> mandatFordeling(PartiKvotient[] kvotienter) {
		Map<String, Integer> partiMandatMap = new TreeMap<>();
		for (PartiKvotient kvotient : kvotienter) {
			String partikode = kvotient.getPartikode();
			if (partiMandatMap.containsKey(partikode)) {
				Integer antallMandaterForPartiet = partiMandatMap.get(partikode);
				partiMandatMap.put(partikode, antallMandaterForPartiet + 1);
			} else {
				partiMandatMap.put(partikode, 1);
			}
		}
		return partiMandatMap;
	}
}
