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

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;

import no.valg.valgnatt.backend.mandatberegning.Logg;
import no.valg.valgnatt.backend.mandatberegning.domene.KommuneKrets;
import no.valg.valgnatt.backend.mandatberegning.domene.KommuneKretsParti;
import no.valg.valgnatt.backend.mandatberegning.domene.Kvotient;
import no.valg.valgnatt.database.Konstanter;
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.Fylke;
import no.valg.valgnatt.database.geografi.Kommune;
import no.valg.valgnatt.database.geografi.Land;
import no.valg.valgnatt.database.geografi.Område;
import no.valg.valgnatt.database.geografi.PartiMandater;
import no.valg.valgnatt.database.geografi.Strata;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;


/**
 * MSI jun 2016:
 * Prognose representerer et område man ønsker å beregne prognose på. Dette området
 * kan være hele landet, et fylke eller en kommune. Her foregår det en god del snask, så
 * det kan lønne seg å lese kommentarene.
 */
public class Prognose {

	// Dersom flagget er satt til "true" så benyttes apriori/statiske beta-koeffisienter (angitt i MatriseKalkulasjon).
	// Dette skal i prinsippet gi en litt dårligere prognose, så med mindre man driver testing så bør flagget være satt til "false".
	static final boolean STATISK_BETA_KOEFFISIENTER = false;
	// Korreksjon mellom betaberegnet S (Sample) og A (hele området) kan brukes i prosentpoeng (default) eller prosentvis. Fordi
	// Senterpartiet ofte går høyt i Sample tidlig i opptellingen, så kan det oppstå en stor korreksjon (>> 1 pp). For
	// store *positive* korreksjoner har det vist seg å være bedre å regne dem om til prosentvise
	// korreksjoner. Ettersom betaberegnet S og A blir mer og mer like når Samplet vokser i størrelse, så synker korreksjonen
	// under denne grensen og vi går uansett tilbake til prosentpoeng korreksjon. Dersom man ønsker å la være å gjøre
	// prosentvis korreksjon, så settes grensen til BigDecimal("1") (=100%). Ellers er anbefalt verdi "0.02" (2%).
	private static final BigDecimal PP_KORREKSJON_ØVRE_GRENSE = new BigDecimal("0.02");
	private static final Logger LOG = LogManager.getLogger(Prognose.class);
	private static final int HUNDRE = 100;
	private static final int MIN_STEMMER_PR_KRETS = 1000;
	public static final double SEKSTISYV_PROSENT = 0.67;private static String[] kommgruppe1 = new String[]{"0121", "0127", "0128", "0402", "0412", "0419", "0420", "0423", "0425", "0426", "0428", "0429", "0430", "0432",
			"0434", "0436", "0437", "0438", "0439", "0441", "0511", "0512", "0513", "0514", "0515", "0516", "0517", "0519", "0520", "0521", "0522", "0529", "0540",
			"0541", "0542", "0543", "0544", "0545", "0615", "0616", "0617", "0618", "0619", "0620", "0621", "0622", "0631", "0632", "0633", "0728", "0805", "0814",
			"0815", "0817", "0819", "0821", "0826", "0827", "0828", "0829", "0830", "0831", "0833", "0834", "0938", "0941", "1021", "1026", "1027", "1034", "1046",
			"1114", "1119", "1120", "1129", "1133", "1134", "1141", "1142", "1145", "1160", "1211", "1227", "1231", "1233", "1234", "1241", "1244", "1252", "1411",
			"1412", "1417", "1418", "1422", "1428", "1429", "1430", "1431", "1432", "1433", "1438", "1439", "1441", "1444", "1445", "1449", "1502", "1504", "1505",
			"1511", "1514", "1515", "1524", "1543", "1545", "1546", "1548", "1554", "1557", "1560", "1566", "1567", "1571", "1573", "1576", "1612", "1613", "1617",
			"1620", "1622", "1624", "1627", "1630", "1632", "1633", "1634", "1635", "1636", "1638", "1644", "1648", "1653", "1657", "1664", "1665", "1711", "1714",
			"1717", "1718", "1719", "1721", "1724", "1725", "1736", "1738", "1739", "1740", "1742", "1743", "1744", "1748", "1749", "1750", "1751", "1755", "1811",
			"1812", "1815", "1816", "1818", "1822", "1825", "1826", "1827", "1828", "1832", "1834", "1835", "1836", "1837", "1839", "1845", "1848", "1856", "1857",
			"1859", "1867", "1874", "1915", "1917", "1926", "1927", "1928", "1929", "1933", "1936", "1938", "1940", "1943", "2011", "2014"};
	private static String[] kommgruppe2 = new String[]{"0101", "0104", "0105", "0106", "0111", "0118", "0119", "0122", "0123", "0124", "0125", "0135", "0136", "0137",
			"0138", "0211", "0213", "0214", "0215", "0216", "0217", "0219", "0220", "0221", "0226", "0227", "0228", "0229", "0230", "0231", "0233", "0234", "0235",
			"0236", "0237", "0238", "0239", "0301", "0403", "0415", "0417", "0418", "0427", "0501", "0502", "0528", "0532", "0533", "0534", "0536", "0538", "0602",
			"0604", "0605", "0612", "0623", "0624", "0625", "0626", "0627", "0628", "0701", "0702", "0704", "0706", "0709", "0710", "0711", "0713", "0714", "0716",
			"0719", "0720", "0722", "0723", "0806", "0807", "0811", "0822", "0901", "0904", "0906", "0911", "0912", "0914", "0919", "0926", "0928", "0929", "0935",
			"0937", "0940", "1001", "1002", "1003", "1004", "1014", "1017", "1018", "1029", "1032", "1037", "1101", "1102", "1103", "1106", "1111", "1112", "1121",
			"1122", "1124", "1127", "1130", "1135", "1144", "1146", "1149", "1151", "1201", "1216", "1219", "1221", "1222", "1223", "1224", "1228", "1232", "1235",
			"1238", "1242", "1243", "1245", "1246", "1247", "1251", "1253", "1256", "1259", "1260", "1263", "1264", "1265", "1266", "1401", "1413", "1416", "1419",
			"1420", "1421", "1424", "1426", "1443", "1516", "1517", "1519", "1520", "1523", "1525", "1526", "1528", "1529", "1531", "1532", "1534", "1535", "1539",
			"1547", "1551", "1563", "1601", "1621", "1640", "1662", "1663", "1702", "1703", "1756", "1804", "1805", "1813", "1820", "1824", "1833", "1838", "1840",
			"1841", "1849", "1850", "1851", "1852", "1853", "1854", "1860", "1865", "1866", "1868", "1870", "1871", "1901", "1902", "1903", "1911", "1913", "1919",
			"1920", "1922", "1923", "1924", "1925", "1931", "1939", "1941", "1942", "2002", "2003", "2004", "2012", "2015", "2017", "2018", "2019", "2020", "2021",
			"2022", "2023", "2024", "2025", "2027", "2028", "2030"};

    private static boolean strataSjekkUtført = false;
    private Område område; // område som skal regnes prognose for
    private Set<String> betaPartier; // beta-koeffisientene regnes på bakgrunn av betapartiene, men alle partier er omfattet av prognosen
    private Set<String> partikoderSomStillerTilValg;

    public Prognose(Område område, Set<String> betaPartier, Set<String> partikoderSomStillerTilValg) {
        this.område = område;
        this.betaPartier = betaPartier;
        this.partikoderSomStillerTilValg = partikoderSomStillerTilValg;

    }

    private static void sjekkStrata(Land land, List<Strata> landsdeler) {
        if (!strataSjekkUtført) {
            loggManglendeKommunerIStrata(land, landsdeler);
            strataSjekkUtført = true;
        }
    }

    private static void loggManglendeKommunerIStrata(Land land, List<Strata> landsdeler) {
        for (Kommune k : land.getKommuner().values()) {
            boolean kommuneEksistererIStrata = landsdeler.stream().anyMatch(landsdel -> landsdel.getKommune(k.getNr()) != null);
            if (!kommuneEksistererIStrata) {
                LOG.warn("Stratifiseringen mangler kommune " + k.getNr() + " (" + k.getNavn() + ")");
            }
        }
    }

    public Map<String, Kvotient> beregn() {
        Map<String, Kvotient> prognose = null;
        if (område.getClass() == Land.class) {
            prognose = beregnLandsprognoseBasertPå2KommuneStrata((Land) område);
        } else if (område.getClass() == Fylke.class) {
            prognose = beregnPrognoseBasertPå2KommunetypeStrata((Fylke) område);
        }
        if (prognose == null || prognose.isEmpty()) {
            prognose = beregnOmråde();
        }
        Logg.blankeKorrigert(prognose, område.getNr(), betaPartier);
        return prognose;
    }

    private Map<String, Kvotient> beregnLandsprognoseBasertPå2KommuneStrata(Land land) {
        List<Strata> landsdeler = new ArrayList<>();
        landsdeler.add(land.lagStrata("K1", Konstanter.STRATA_MIN_KRETSER, kommgruppe1));
        landsdeler.add(land.lagStrata("K2", Konstanter.STRATA_MIN_KRETSER, kommgruppe2));
        sjekkStrata(land, landsdeler);
        return beregnPrognoseBasertPåLandsdeler(land, betaPartier, landsdeler);
    }

    private Map<String, Kvotient> beregnPrognoseBasertPå2KommunetypeStrata(Fylke fylke) {
        List<Strata> landsdeler = new ArrayList<>();
        landsdeler.add(fylke.lagStrata("K1", Konstanter.MIN_KRETSER, kommgruppe1));
        landsdeler.add(fylke.lagStrata("K2", Konstanter.MIN_KRETSER, kommgruppe2));
        return beregnPrognoseBasertPåLandsdeler(fylke, betaPartier, landsdeler);
    }


    private Map<String, Kvotient> beregnPrognoseBasertPåLandsdeler(Område område, Set<String> betaPartier, List<Strata> landsdeler) {
        List<Map<String, Kvotient>> prognoser = new ArrayList<>();

        Set<String> partikoderPåValg = område.getValgdistrikt().getPartiMandater().stream()
                .filter(PartiMandater::isStillerliste)
                .map(PartiMandater::getPartikode)
                .collect(Collectors.toCollection(HashSet::new));
        for (Strata l : landsdeler) {
            Map<String, Kvotient> prognose = new Prognose(l, betaPartier, partikoderPåValg).beregnOmråde();
            if (prognose.isEmpty()) {
                return null;
            }
            prognoser.add(prognose);
        }

        // Alle landsdeler har en prognose - nå skal de slås sammen til en landsprognose
        Strata landsdel = landsdeler.get(0);
        Map<String, Kvotient> prognose = prognoser.get(0);
        for (int i = 1; i < landsdeler.size(); i++) {
            Strata nyStrata = landsdeler.get(i);
            Map<String, Kvotient> nyPrognose = prognoser.get(i);
            BigDecimal vekt = Vekt.beregnFrammøteVekt(område, landsdel.getKommuner().keySet(), nyStrata.getKommuner().keySet());
            Map<String, Kvotient> sammenslåttPrognose = Vekt.slåSammenPrognoser(prognose, nyPrognose, vekt);
            Logg.sammenslåing(prognose, landsdel.getNr(), nyPrognose, nyStrata.getNr(), sammenslåttPrognose, landsdel.getNr() + nyStrata.getNr(), vekt, landsdel
                    .getNr() + nyStrata.getNr(), betaPartier);
            landsdel.leggTilId(nyStrata.getNr());
            landsdel.leggTilKommuner(nyStrata.getKommuner());
            prognose = sammenslåttPrognose;
        }
        return prognose;
    }

    private Map<String, Kvotient> beregnOmråde() {


        // 0. Uttrekk av data til prognoseberegningene (se kommentar til uttrekksmetoden)
        Map<KommuneKretsParti, Integer> sampleFOK = PrognoseInndata.lag(område, Valg.DETTE, null);

        // 1. Sjekker om det er for tidlig å beregne prognose (grunnet for lite data til å lage en bedre prognose enn
        // å bruke opptalte stemmer).
        if (forTidligForPrognose(sampleFOK, område.getMinimumKretserForPrognose())) {
            return new TreeMap<>();
        }

        // 0. Uttrekk av data til prognoseberegningene (se kommentar til uttrekksmetoden)
        Map<KommuneKretsParti, Integer> fhsFOK = PrognoseInndata.lag(område, Valg.DETTE, ValgtingForhand.F);
        Map<KommuneKretsParti, Integer> sisteFOK = PrognoseInndata.lag(område, Valg.SISTE, null);
        Map<KommuneKretsParti, Integer> nestSisteFOK = PrognoseInndata.lag(område, Valg.NESTSISTE, null);

        // 2. Finner vekter som avgjør hvordan forskjellge delprognoser skal summeres. Vektene er forholdsvis viktige
        // for å lage en god prognose, hvilket betyr at jo mer korrekt de representerer forholdet de skal vekte mellom,
        // jo bedre blir prognosen.
        Logg.prognoseberegningInndata(område, sampleFOK);

        // 4. Regner ut FOK-prognose
        return prognose(sampleFOK, betaPartier, fhsFOK, sisteFOK, nestSisteFOK);
    }


    // Stemmetall = kvotient * fremmøteprosent * antallstemmeberettigede. Denne metoden kan du kjøre
    // etter at du har fått et prognose-map fra beregnOmråde()
    public void oppdaterPartiMandaterMedPrognoseStemmetall(Map<String, Kvotient> kvotientPrParti) {
        Float frammøteProsent = område.beregnFrammoteProsentIaar();
        if (frammøteProsent == null) {
            frammøteProsent = Konstanter.DEFAULT_FRAMMØTE_PROSENT;
        }
        int antallFrammøtte = (int) (område.getAntallStemmeberettigede() * frammøteProsent);
        for (String partikode : kvotientPrParti.keySet()) {
            Kvotient kvotient = kvotientPrParti.get(partikode);
            PartiMandater partiMandater = område.getValgdistrikt().getPartiMandater(partikode);
            partiMandater.getPrognoseData().setStemmer((int) (kvotient.getKvotient().doubleValue() * antallFrammøtte));
            partiMandater.getPrognoseData().setPrognose("1");
        }
    }


    /**
     * Dersom vi har færre enn Konstanter.MIN_SAMPLE_KRETSER kretser i FOK/DOK-sample eller for få stemmer, så regner vi ikke ut prognose
     * En prognose på et fullført Oslo-fylke, eller på en enkelt kommune vil ende opp med bare én krets når alle kretser er opptalt,
     * så vi sjekker i tillegg opptalt-prosenten.
     */
    // S1244: tillater sammenligning av float == 1f, fordi vi setter 1f som verdi et annet sted i koden
    @java.lang.SuppressWarnings({"squid:S1244"})
    private boolean forTidligForPrognose(Map<KommuneKretsParti, Integer> sampleFOK, int minKretser) {
        boolean forFåKretser = Diverse.finnKretser(sampleFOK).size() < minKretser;
        boolean forFåStemmer = sampleFOK.values().stream().mapToInt(i -> i).sum() < minKretser * MIN_STEMMER_PR_KRETS;
        boolean fulltOpptalt = område.beregnOpptaltProsent(ForeløpigEndeligOppgjør.FORELØPIG, null) > SEKSTISYV_PROSENT;
        return (forFåKretser || forFåStemmer) && !fulltOpptalt;
    }

    /**
     * MSI jun 2016:
     *
     * Metoden beregner prognose for alle partier i som finnes i sample, selv om vi kan/bør forvente noe bedre prognoser for betapartiene.
     */
    @SafeVarargs
    private final Map<String, Kvotient> prognose(Map<KommuneKretsParti, Integer> sample, Set<String> betaPartikoder, Map<KommuneKretsParti, Integer>... prediktiveStemmerPrKKPArr) {
        // Hvis sample er tomt -> ingen prognose
        if (sample.size() == 0) {
            return new TreeMap<>();
        }

        // Beregner beta-koeffisienter
        double[] betaer = beregnBetaKoeffisienter(sample, betaPartikoder, prediktiveStemmerPrKKPArr);
        // Finner andeler pr parti i sample ved å telle stemmene vi har i sample
        Map<String, Kvotient> andelerSample = Diverse.andeler(Diverse.aggregerStemmerPrParti(sample));
        // Gjør en betaberegning for sample
        Map<String, Kvotient> betaSample = betaBeregnSample(sample, prediktiveStemmerPrKKPArr, betaer, andelerSample);
        // Gjør en betaberegning for alle kretser
        Map<String, Kvotient> betaAlle = betaBeregnAlle(prediktiveStemmerPrKKPArr, betaer);
        // Prognosen fremkommer ved å finne korreksjonen mellom betaSample og betaAlle og deretter legge til andeler i sample
        Map<String, Kvotient> prognose = beregnKorreksjon(andelerSample, betaSample, betaAlle, område.getNr());
        // Det kan ha blitt negative andeler etter korreksjone og vi må uansett sikre oss at andelene totalt sett går opp i 100%
        Map<String, Kvotient> prognosePolert = Diverse.fordelAndelerPåHundreProsent(Diverse.fjernNegativeAndeler(prognose));

        Logg.andelBeregning(andelerSample, betaSample, betaAlle, prognose, prognosePolert, område.getNr(), betaPartikoder);
        return prognosePolert;
    }

    /**
     * Betakoeffisienter er de koeffisientene som gir den beste sammenhengen mellom sample og prediktive stemmer for betapartiene.
     * Sammenhengen uttrykkes slik (og vi kaller dette for "betaberegningen"):
     *
     * Partiandel(sample, betaparti) = B1*Partiandel(prediktiv1, betaparti) + B2*Partiandel(prediktiv2, betaparti) + ...
     */
    @SuppressWarnings("unchecked")
    private double[] beregnBetaKoeffisienter(Map<KommuneKretsParti, Integer> sample, Set<String> betaPartikoder, Map<KommuneKretsParti, Integer>[] prediktiveStemmerPrKKPArr) {
        Map<KommuneKretsParti, Kvotient> andelerISamplePrKKP = Diverse.andelerForBetaPartier(sample, betaPartikoder);
        TreeMap<KommuneKretsParti, Kvotient>[] prediktiveAndelerPrKKPArr = new TreeMap[prediktiveStemmerPrKKPArr.length];
        int i = 0;
        for (Map<KommuneKretsParti, Integer> prediktiveStemmerPrKKP : prediktiveStemmerPrKKPArr) {
            prediktiveAndelerPrKKPArr[i++] = Diverse.andelerForBetaPartier(prediktiveStemmerPrKKP, betaPartikoder);
        }
        double[] betaer = MatriseKalkulasjon.beregnBetaverdier(andelerISamplePrKKP, prediktiveAndelerPrKKPArr);
        Logg.matrise(betaer, område.getNr(), sample, prediktiveStemmerPrKKPArr);
        return betaer;
    }

    /**
     * Gjør "betaberegning" for sample. Dvs. at man bruker de prediktive stemmene, men bare for de kretsene som finnes i sample.
     */
    @SuppressWarnings("unchecked")
    private TreeMap<String, Kvotient> betaBeregnSample(Map<KommuneKretsParti, Integer> sample, Map<KommuneKretsParti, Integer>[] prediktiveStemmerPrKKPArr, double[] betaer, Map<String, Kvotient> andelerISamplePrParti) {
        TreeMap<String, Kvotient>[] prediktiveAndelerISamplePrPartiArr = new TreeMap[prediktiveStemmerPrKKPArr.length];
        int i = 0;
        Set<KommuneKrets> sampleKretser = Diverse.finnKretser(sample);
        for (Map<KommuneKretsParti, Integer> prediktiveStemmerPrParti : prediktiveStemmerPrKKPArr) {
            prediktiveAndelerISamplePrPartiArr[i++] = Diverse.andeler(Diverse.aggregerStemmerPrParti(Diverse.filtrerKretser(prediktiveStemmerPrParti, sampleKretser)));
        }
        return beregnPaBeta(betaer, andelerISamplePrParti.keySet(), prediktiveAndelerISamplePrPartiArr);
    }

    /**
     * Gjør betaberegning for alle kretser. Gjør i prinsippet det samme som betaBeregnSample()
     */
    @SuppressWarnings("unchecked")
    private TreeMap<String, Kvotient> betaBeregnAlle(Map<KommuneKretsParti, Integer>[] prediktiveStemmerPrKKPArr, double[] betaer) {
        TreeMap<String, Kvotient>[] prediktiveAndelerPrPartiArr = new TreeMap[prediktiveStemmerPrKKPArr.length];
        int i = 0;
        for (Map<KommuneKretsParti, Integer> prediktiveStemmerPrParti : prediktiveStemmerPrKKPArr) {
            prediktiveAndelerPrPartiArr[i++] = Diverse.andeler(Diverse.aggregerStemmerPrParti(prediktiveStemmerPrParti));
        }
        return beregnPaBeta(betaer, null, prediktiveAndelerPrPartiArr);
    }

    /**
     * MSI jun 2016:
     *
     * @param paSampleStemmer - partiandel basert på alle VTS/FHS i sample (konvergerer mot resultat når Sample er tilnærmet lik Alle kretser)
     * @param paSampleBeta    - partiandel basert på beta-beregning (se beregnPaBeta()) av partier i sample (konvergerer mot paAlleBeta når Sample er tilnærmet lik Alle
     *                           kretser)
     * @param paAlleBeta      - partiandel basert på beta-beregning (se beregnPaBeta()) av partier i alle kretser
     * @return - den korrigerte partiandelen regnet ut etter formelen: paKorrigert = paSampleStemmer - paSampleBeta + paAlleBeta
     * <p/>
     * Formelen gir altså til å begynne med en korreksjonsfaktor "paSampleStemmer - paSampleBeta" som legges til paAlleBeta - for å gi det
     * prognostiserte oppmøte for et parti basert på alle kretser. Ettersom valget skrider frem, så vil antall kretser som er med i Sample
     * øke, og paSampleBeta og paAlleBeta vil bli til slutt helt lik. Da sitter vi igjen med paSampleStemmer, som er et rent faktisk resultat,
     * ikke en prognose. Ergo ser vi at formelen gjør at vi konvereger mot faktisk stemmeresultat til slutt i valget, og får en korreksjonsfaktor
     * tidlig i valget, som vi benytter i alle kretser.
     */
    private Map<String, Kvotient> beregnKorreksjon(Map<String, Kvotient> paSampleStemmer, Map<String, Kvotient> paSampleBeta, Map<String, Kvotient> paAlleBeta, String områdeNr) {
        Map<String, Kvotient> paSampleKorrigert = new TreeMap<>();
        for (String partikode : paSampleStemmer.keySet()) {
            BigDecimal s = paSampleStemmer.get(partikode).getKvotient();
            BigDecimal paKorrigert = s;
            if (paSampleBeta.containsKey(partikode) && paAlleBeta.containsKey(partikode)) { // normaltilfellet
                BigDecimal ps = paSampleBeta.get(partikode).getKvotient();
                BigDecimal pa = paAlleBeta.get(partikode).getKvotient();
                // MSI jun 2016:
                // Modifikasjon: Dersom pa << ps, så finn prosentvis endring fra ps til s og bruk dette som korreksjonsfaktor for pa
                // Grunnen til at vi ikke gjøre dette for pa >> ps, er at denne type korreksjoner vil overkorrigere pga av et lite
                // sample. I alle andre tilfeller bruker vi den gamle metoden (else-tilfellet)
                if (pa.compareTo(ps.subtract(PP_KORREKSJON_ØVRE_GRENSE)) < 0) {

                    BigDecimal korreksjonsfaktor = BigDecimal.ONE.add(s.subtract(ps).divide(ps, Konstanter.MAKS_PRESISJON, BigDecimal.ROUND_HALF_UP));
                    paKorrigert = korreksjonsfaktor.multiply(pa);

                    float relKorrPst = (korreksjonsfaktor.floatValue() - 1) * pa.floatValue() * HUNDRE;
                    float ppKorrPst = s.floatValue() * HUNDRE - ps.floatValue() * HUNDRE;
                    LOG.info(Logg.prefix(områdeNr) + String.format("Partikode: %3s REL-korreksjon: %4.2f istedet for PP-Korreksjon %4.2f gir Prognose: %4.2f",
                            partikode, relKorrPst, ppKorrPst, paKorrigert.floatValue() * HUNDRE));
                } else {
                    paKorrigert = s.subtract(ps).add(pa); // konvergerer mot resultat s nærmer seg alle kretser (ps blir da lik pa)
                }
            }
            // Dersom partiet finnes i sample men ikke i fhs/hs, så vil dette sørge for at man bruker sample ukorrigert. 
            paSampleKorrigert.put(partikode, new Kvotient(paKorrigert));
        }
        // MSI okt 2017:
        // Dersom partiet finnes i fhs/hs OG stiller til valg i gjeldende valgdistrikt, men ikke finnes i sample, 
        // så vil dette sørge for at man bruker beta-Alle ukorrigert
        for (String partikode : paAlleBeta.keySet()) {
            if (!paSampleKorrigert.containsKey(partikode) && partikoderSomStillerTilValg.contains(partikode)) {
                paSampleKorrigert.put(partikode, new Kvotient(paAlleBeta.get(partikode).getKvotient()));
            }
        }
        return paSampleKorrigert;
    }


    @SafeVarargs
    private final TreeMap<String, Kvotient> beregnPaBeta(double[] betaVerdier, Set<String> partikoder, Map<String, Kvotient>... prediktiveAndelerPrPartiArr) {
        TreeMap<String, Kvotient> paBeta = new TreeMap<>();
        int i = 1;
        for (Map<String, Kvotient> prediktiveAndelerPrParti : prediktiveAndelerPrPartiArr) {
            for (String partikode : prediktiveAndelerPrParti.keySet()) {
                if (partikoder == null || partikoder.contains(partikode)) {
                    Kvotient d = prediktiveAndelerPrParti.get(partikode).multiply(BigDecimal.valueOf(betaVerdier[i]));
                    paBeta.put(partikode, paBeta.get(partikode) == null ? d : paBeta.get(partikode).add(d));
                }
            }
            i++;
        }
        return paBeta;
    }
}
