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

import no.valg.valgnatt.backend.mandatberegning.domene.Kvotient;
import no.valg.valgnatt.backend.mandatberegning.domene.PartiKvotient;

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

import static java.math.BigDecimal.ROUND_HALF_UP;

class MandatberegningFelles {

	private static final BigDecimal SAINTE_LAGUE_MODIFISERTE_DELINGSTALL = new BigDecimal("1.4");
	private static final int KVOTIENT_PRESISJON = 5;

	private MandatberegningFelles() {
	}

	/**
	 * Returnerer en liste av parti + restkvotient sortert synkende etter restkvotientens størrelse.
	 * Restkvotientene beregnes ved å dele partiets stemmer på et delingstall, hvor delingstallene er
	 * gitt av Sainte-Lagües modifiserte metode (1.4, 3, 5, 7, 9, 11,...). Sørger for at BLANKE-partiet
	 * aldri blir regnet med, siden dette dreier seg om mandatfordeling.
	 * <p/>
	 * Listens størrelse er lik antall partier * antall mandater, men kan avkortes til de X første
	 * elementene når den returneres (gitt av @param antReturKvotienter)
	 * <p/>
	 * Eksempel (gitt at det er kun 2 mandater) - dette eksempelet blir brukt videre i andre
	 * kommentarer i denne klassen.
	 * <p/>
	 * partyVotes:
	 * A 300
	 * H 200
	 * FRP 150
	 * KRF 50
	 * <p/>
	 * return : (i både brøkform (første linje) og desimalform (andre linje))
	 * [300/1.4 (A), 200/1.4 (H), 150/1.4 (FRP), 300/3 (A), 200/3 (H), 150/3 (FRP), 50/1.4 (KRF), 50/3  (KRF)]
	 * [214.29  (A),  142.86 (H),  107.14 (FRP),   100 (A), 66.67 (H),    50 (FRP),  35.71 (KRF), 16.67 (KRF)]
	 */
	static PartiKvotient[] sorterteKvotienter(int antallMandater, Map<String, Integer> partiStemmeMap, Integer antReturKvotienter) {
		int antallKvotienter = antallMandater * partiStemmeMap.size();
		PartiKvotient[] kvotienter = new PartiKvotient[antallKvotienter];
		int index = 0;
		for (int n = 0; n < antallMandater; n++) {
			BigDecimal delingstall = getSainteLagueDelingstall(n);
			for (Map.Entry<String, Integer> partiStemme : partiStemmeMap.entrySet()) {
				PartiKvotient partiKvotient = new PartiKvotient(partiStemme.getKey(), new Kvotient(partiStemme.getValue()).divide(delingstall), delingstall);
				partiKvotient.setStemmetall(partiStemme.getValue()); // Vi trenger stemmetallet, fordi vi skal sortere denne senere
				kvotienter[index++] = partiKvotient;
			}
		}
		Arrays.sort(kvotienter);
		if (antReturKvotienter != null && antReturKvotienter > antallMandater) {
			return begrensAntallKvotienter(kvotienter, antReturKvotienter);
		}
		return kvotienter;
	}

	private static BigDecimal getSainteLagueDelingstall(int n) {
		return n == 0 ? SAINTE_LAGUE_MODIFISERTE_DELINGSTALL : new BigDecimal(2 * n + 1);
	}

	static PartiKvotient[] begrensAntallKvotienter(PartiKvotient[] kvotienter, int antallKvotienter) {
		if (antallKvotienter <= 0 || antallKvotienter >= kvotienter.length) {
			return kvotienter;
		}
		PartiKvotient[] res = new PartiKvotient[antallKvotienter];
		System.arraycopy(kvotienter, 0, res, 0, antallKvotienter);
		return res;
	}

	static Map<String, BigDecimal> høyesteKvotienterUtenMandat(int antallMandater, PartiKvotient[] kvotienter) {
		Map<String, BigDecimal> kvotientMap = new TreeMap<>();

		for (PartiKvotient kvotient : kvotienter) {
			String partikode = kvotient.getPartikode();
			PartiKvotient høyesteKvotient = finnHøyesteKvotientUtenMandat(antallMandater, kvotienter, partikode);
			if (høyesteKvotient != null) {
				kvotientMap.put(partikode, høyesteKvotient.getKvotient().getKvotient().setScale(KVOTIENT_PRESISJON, ROUND_HALF_UP));
			}
		}

		return kvotientMap;
	}

	private static PartiKvotient finnHøyesteKvotientUtenMandat(int antallMandater, PartiKvotient[] sorterteStemmetallskvotienter, String partikode) {
		for (int i = antallMandater; i < sorterteStemmetallskvotienter.length; i++) {
			PartiKvotient pq = sorterteStemmetallskvotienter[i];
			if (pq.getPartikode().equals(partikode)) {
				return pq;
			}
		}
		return null;
	}

	static Map<String, BigDecimal> lavesteKvotienterMedMandat(int antallMandater, PartiKvotient[] kvotienter) {
		Map<String, BigDecimal> kvotientMap = new TreeMap<>();

		for (PartiKvotient kvotient : kvotienter) {
			String partikode = kvotient.getPartikode();
			PartiKvotient lavesteKvotient = finnLavesteKvotientMedMandat(antallMandater, kvotienter, partikode);
			if (lavesteKvotient != null) {
				kvotientMap.put(partikode, lavesteKvotient.getKvotient().getKvotient().setScale(KVOTIENT_PRESISJON, ROUND_HALF_UP));
			}
		}

		return kvotientMap;
	}
	
	private static PartiKvotient finnLavesteKvotientMedMandat(int antallMandater, PartiKvotient[] sorterteStemmetallskvotienter, String partikode) {
		PartiKvotient lavesteKvotientMedMandat = null;
		for (int i = 0; i < antallMandater; i++) {
			PartiKvotient pq = sorterteStemmetallskvotienter[i];
			if (pq.getPartikode().equals(partikode)) {
				lavesteKvotientMedMandat = pq;
			}
		}
		return lavesteKvotientMedMandat;
	}

	static Map<String, Integer> lavesteMandatrangeringerMedMandat(PartiKvotient[] kvotienter) {
		Map<String, Integer> mandatrangPrParti = new TreeMap<>();
		for (int i = 0; i < kvotienter.length; i++) {
			mandatrangPrParti.put(kvotienter[i].getPartikode(), i + 1);
		}

		return mandatrangPrParti;
	}

	static Map<String, Integer> høyesteMandatrangeringerUtenMandat(int antallMandater, PartiKvotient[] kvotienter) {
		Map<String, Integer> mandatrangPrParti = new TreeMap<>();

		for (int i = antallMandater; i < kvotienter.length; i++) {
			if (!mandatrangPrParti.containsKey(kvotienter[i].getPartikode())) {
				mandatrangPrParti.put(kvotienter[i].getPartikode(), i + 1);
			}
		}

		return mandatrangPrParti;
	}
}
