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

import java.util.Collection;
import java.util.Map;
import java.util.TreeMap;

import no.valg.valgnatt.backend.mandatberegning.domene.KommuneKretsParti;
import no.valg.valgnatt.database.domene.ForeløpigEndeligOppgjør;
import no.valg.valgnatt.database.domene.Valg;
import no.valg.valgnatt.database.domene.ValgtingForhand;
import no.valg.valgnatt.database.geografi.Kommune;
import no.valg.valgnatt.database.geografi.Område;
import no.valg.valgnatt.database.geografi.PartiStemmer;
import no.valg.valgnatt.database.hibernate.Stemmekrets;
import no.valg.valgnatt.database.hibernate.StemmekretsId;

class PrognoseInndata {


	private static final float TRETTI_PROSENT = 0.3f;
	private static final float SEKSTISYV_PROSENT = 0.67f;

	private PrognoseInndata() {
	}

	/**
	 * MSI juni 2016:
	 *
	 * Denne metoden er kritisk viktig fordi den brukes til å lage korrekt uttrekk av partier/stemmer
	 * som i sin tur brukes som inndata til prognoseberegningen (PB). Man må egentlig forstå PB for å
	 * kunne forstå hvorfor vi trenger akkurat et bestemt sett av inndata. Jeg forutsetter derfor et
	 * visst minimum av kunnskap om PB for å kunne forstå denne kommentaren, men du kommer ganske langt
	 * selv uten dette minimumet:
	 *
	 * Inndata til én PB består av tre deler:
	 *
	 * 1) Sample (S)      - stemmer i kretser som er ferdig opptalt
	 * 2) Prediksjon P(S) - historiske-/forhåndsstemmer for kretser i Sample (S)
	 * 3) Prediksjon P(A) - historiske-/forhåndsstemmer for kretser i hele området (A)
	 *
	 * Enkelt sagt så regner man ut P(S) - P(A) og finner Korreksjon (K) for hvert parti.
	 * K uttrykker da forskjellen mellom en prediksjon regnet bare med Sample-kretser og prediksjonen
	 * regnet med hele området. Dersom man grubler lenge og vel så forstår man at denne korreksjonen da kan
	 * legges til de andelene man finner for hvert parti i Sample og voila! - man har prognose!!
	 *
	 * Det som implisitt sies i et slikt regnestykke er at *endringene* (altså fremgang/tilbakegang)
	 * som man finner i forskjellen mellom P(S) og P(A) er prediktive, altså at de kan brukes
	 * på hele området. P kan i prinsippet regnes ut på mange måter, f.eks. bare benytte historiske
	 * stemmer, fra ett eller flere historiske valg, eller de kan også inneholde forhåndsstemmer. Hoved-
	 * poenget er at man gjør den samme regneoperasjonen for P(S) som for P(A)!!
	 *
	 * Fra 2013-valget så viser nokså grundige undersøkelser at kombinasjonen av forhåndsstemmer og to
	 * historiske valg gir den korreksjon som har lavest standardavvik - dvs. den korreksjon som
	 * er mest prediktiv. Dette er da også nesten i tråd med den måten prognosen har vært beregnet siden
	 * 2003 (da brukte man 3 historiske valg + FHS).
	 *
	 * Ideelt sett hadde alle valg hatt det samme antall kretser og hatt både valgting- og forhåndsstemmer
	 * i alle kretsene. Imidlertid er ikke verden ideell (bummer!). Følgende begrensninger og utfordringer må
	 * håndteres:
	 *
	 * a) Forhåndsstemmer telles opp sentralt (krets 0000) og finnes dermed ikke i alle kretser
	 * b) Antall kretser kan variere fra valg til valg
	 * c) Noen kommuner/byer er svært store og det tar lang tid fra de begynner opptellingen til hele kommunen er fullt opptalt
	 *
	 * For å håndtere a) og b) så må PB'en gjøres med hele kommuner. (Og NEI, du kan ikke fordele
	 * forhåndsstemmer utover de enkelte kretsene i kommunen for å løse problemet - fordi hver krets
	 * kan stemme enormt forskjellig, noe som vil føre til store feilaktige korreksjoner mot jevnt fordelte
	 * forhåndsstemmeandeler i hver krets.) Det betyr videre at S kun består av fullt opptalte kommuner,
	 * og at A (hele området) inneholder alle kommuner som har både forhåndsstemmer
	 * og historiske stemmer. Imidlertid ignorerer vi da potensielt c), noe som får uheldige
	 * konsekvenser ettersom det kan ligge mange opptalte valgtingstemmer i slike kommuner som ikke blir
	 * regnet med. Da "taper" prognosen mot opptalte-stemmer-prognose. Dette løser vi ved å anse slike
	 * delvis opptalte kommuner som fullt opptalt så fort kommunen er over 67 i opptaltprosent og området som helhet
	 * har over 30 i opptaltprosent.
	 *
	 * Følgende uttrekk må da gjøres for å skaffe til veie de nødvendige data til prognosenberegningen:
	 *
	 * 1) S    : valg = Valg.DETTE,     vf = null
	 * 2) P(A) : valg = Valg.DETTE,     vf = ValgtingForhand.F
	 * 3) P(A) : valg = Valg.SISTE,     vf = null
	 * 4) P(A) : valg = Valg.NESTSISTE, vf = null
	 *
	 * Med all denne forklaringen så er metoden forholdsvis enkel: Man samler inn de forskjellige
	 * stemmetallsdata kommune i området og summerer det på 0000-kretsen.
	 */
	static Map<KommuneKretsParti, Integer> lag(Område område, Valg valg, ValgtingForhand vf) {
		Map<KommuneKretsParti, Integer> result = new TreeMap<>();
		float områdeOpptaltProsent = område.beregnOpptaltProsent();
		for (Stemmekrets sk : område.getStemmekretser().values()) {
			if (sk.erDefaultKrets() && (vf == ValgtingForhand.F || valg != Valg.DETTE || fulltOpptaltKommune(sk.getKommune(), områdeOpptaltProsent))) {
				oppdaterPBInndata(valg, vf, result, sk, sk.getKommune().aggregerStemmerPrParti(Område.Summering.TOTAL));
			}
		}
		return result;
	}

	/**
	 * MSI jun 2017
	 * Etter anbefaling fra Jon Helgeland, ser vi kommuner som er 2/3 opptalt som fullt opptalt dersom områdets
	 * opptaltprosent er over 30. Ellers tar vi bare med kommuner som har både FHS og VTS foreløpig opptalt.
	 */
	@java.lang.SuppressWarnings("squid:S1244") // tillater sammenligning av float == 1f fordi verdien settes til 1f et annet sted i koden
	private static boolean fulltOpptaltKommune(Kommune kommune, float områdeOpptaltprosent) {
		float kommuneOpptaltprosent = kommune.beregnOpptaltProsent(ForeløpigEndeligOppgjør.FORELØPIG, null);
		return områdeOpptaltprosent > TRETTI_PROSENT ? kommuneOpptaltprosent > SEKSTISYV_PROSENT : kommuneOpptaltprosent == 1f;
	}

	/**
	 * MSI jun 2016:
	 *
	 * Går igjennom stemmene i partiStemmerListe og legger dem inn med korrekt kommune-krets-parti i resultatet. Det er verdt å merke
	 * seg at dersom et parti får 0 stemmer (som helt klart kan skje) så ignorerer vi dette partiet i denne kretsen. Dette er stort
	 * sett helt korrekt håndtering, fordi vi uansett aggregerer stemmene pr parti etterhvert i prognoseberegningen. Det kan av og til
	 * skape noe forvirring og småfeil, f.eks. dersom det gjøres en null-telling (alle partier får 0 stemmer), for da forsvinner kretsen fra
	 * samplet, selv om den egentlig er opptalt. Sånn totalt sett er det likevel fornuftig å ekskludere alle partier som får 0 stemmer,
	 * det gjør det lettere å håndtere datastrukturene senere.
	 */
	private static void oppdaterPBInndata(Valg valg, ValgtingForhand vf, Map<KommuneKretsParti, Integer> result, Stemmekrets sk, Collection<PartiStemmer>
			partiStemmerListe) {
		for (PartiStemmer PS : partiStemmerListe) {
			int stemmer = PS.getStemmer(valg, vf);
			if (stemmer > 0) {
				StemmekretsId sid = sk.getId();
				result.put(new KommuneKretsParti(sid.getKommnr(), sid.getKretsnr(), PS.getPartikode()), stemmer);
			}
		}
	}
}
