package no.valg.valgnatt.backend.mandatberegning;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

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.backend.mandatberegning.prognose.Diverse;
import no.valg.valgnatt.database.Konstanter;
import no.valg.valgnatt.database.domene.ForeløpigEndeligOppgjør;
import no.valg.valgnatt.database.domene.ValgtingForhand;
import no.valg.valgnatt.database.geografi.Område;

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

public class Logg {
	private static final double HUNDRE = 100d;
	private static final int BREDDE = 149;
	private static final Logger LOG = LogManager.getLogger(Logg.class);
	private static int sisteStemmeskjemaId = 0;


	static void setSisteStemmeskjemaId(int sisteStemmeskjemaId) {
		Logg.sisteStemmeskjemaId = sisteStemmeskjemaId;
	}

	public static void matrise(double[] betaer, String nivaa, Map<KommuneKretsParti, Integer> sample, Map<KommuneKretsParti, Integer>[] prediktiveStemmerPrKKPArr) {
		StringBuilder sb = new StringBuilder(Logg.prefix(nivaa));
		for (int i = 1; i < betaer.length; i++) {
			sb.append("B" + i + ": " + betaer[i] + " ");
		}
		Map<KommuneKrets, Integer> sampleStemmerPrKrets = Diverse.aggregerStemmerPrKrets(sample);
		sb.append("Sample: " + sampleStemmerPrKrets.size() + "/" + sampleStemmerPrKrets.values().stream().mapToInt(a -> a).sum() + " ");
		for (int i = 0; i < prediktiveStemmerPrKKPArr.length; i++) {
			Map<KommuneKretsParti, Integer> pred = prediktiveStemmerPrKKPArr[i];
			Map<KommuneKrets, Integer> predStemmerPrKrets = Diverse.aggregerStemmerPrKrets(pred);
			sb.append("Pred-" + (i + 1) + ": " + predStemmerPrKrets.size() + "/" + predStemmerPrKrets.values().stream().mapToInt(a -> a).sum() + " ");
		}
		LOG.info(sb.toString());
	}

	public static String prefix(String nr) {
		return "PG" + String.format("%-7s", nr) + String.format("%5s: ", sisteStemmeskjemaId);
	}

	/**
	 * Metoden logger de sentrale størrelsene i andelsberegningen, og viser også hvordan regnestykket (grovt sett)
	 * blir utført av implementasjonen. Ved å studere denne loggen får man et godt innblikk i hvordan prognosene
	 * av partiets andel (prosent) fremkommer. Dette er med andre ord den helt sentrale delen av prognosens
	 * sluttprodukt.
	 */
	public static void andelBeregning(
			Map<String, Kvotient> pks,
			Map<String, Kvotient> pksEst,
			Map<String, Kvotient> pkn,
			Map<String, Kvotient> unweighted,
			Map<String, Kvotient> andeler,
			String nivaa,
			Set<String> partikoder) {

		// Heading
		String pfx = prefix(nivaa);
		LOG.info(pfx + String.join("", Collections.nCopies(BREDDE, "-")));
		String allePartinavn = String.format(String.join("", Collections.nCopies(partikoder.size(), "  %-6s")), partikoder.toArray());
		LOG.info(pfx + "                        " + allePartinavn + " Kommentar");
		LOG.info(pfx + String.join("", Collections.nCopies(BREDDE, "-")));
		// Body
		LOG.info(pfx + "  Stemmetall Sample   : " + andelBeregningLinje(partikoder, "Konvergerer mot resultat", pks, BigDecimal.ONE));
		LOG.info(pfx + "- Betaberegnet Sample : " + andelBeregningLinje(partikoder, "Danner korreksjon m/ forrige ledd", pksEst, BigDecimal.ONE));
		LOG.info(pfx + "+ Betaberegnet Alle   : " + andelBeregningLinje(partikoder, "Konvergerer mot Betaberegnet Sample", pkn, BigDecimal.ONE));
		LOG.info(pfx + String.join("", Collections.nCopies(BREDDE, "-")));
		LOG.info(pfx + "= Før normalisering   : " + andelBeregningLinje(partikoder, "Resultatet etter beta-beregningens korreksjon", unweighted, BigDecimal.ONE));
		LOG.info(pfx + "= Resultat            : " + andelBeregningLinje(partikoder, "Har nullet negative andeler/Tilpasset andelene til 100%", andeler, BigDecimal.ONE));
		LOG.info(pfx + String.join("", Collections.nCopies(BREDDE, "-")));
	}

	private static String andelBeregningLinje(Set<String> partikoder, String ledd2, Map<String, Kvotient> andeler, BigDecimal blankeKorreksjon) {
		StringBuilder sb = new StringBuilder();
		for (String partikode : partikoder) {
			if (andeler.get(partikode) != null && andeler.get(partikode).getKvotient() != null) {
				sb.append(String.format("%6.2f |", andeler.get(partikode).getKvotient().divide(blankeKorreksjon, Konstanter.MAKS_PRESISJON, BigDecimal.ROUND_HALF_UP)
						.multiply(BigDecimal.valueOf(HUNDRE))));
			} else {
				sb.append("         |");
			}
		}
		sb.append(" " + ledd2);
		return sb.toString();
	}

	public static void sammenslåing(
			Map<String, Kvotient> prog1,
			String prog1Kommentar,
			Map<String, Kvotient> prog2,
			String prog2Kommentar,
			Map<String, Kvotient> sammenslåttProg,
			String sluttKommentar, BigDecimal vekt,
			String nivaa,
			Set<String> partikoder) {
		String prog1KommModifisert = prog1Kommentar;
		prog1KommModifisert += String.format(" (%4.3f)", vekt);
		String prog2KommModifisert = prog2Kommentar;
		prog2KommModifisert += String.format(" (%4.3f)", BigDecimal.ONE.subtract(vekt));
		LOG.info(prefix(nivaa) + String.format("%-21s : ", prog1KommModifisert) + andelBeregningLinje(partikoder, "", prog1, BigDecimal.ONE));
		LOG.info(prefix(nivaa) + String.format("%-21s : ", prog2KommModifisert) + andelBeregningLinje(partikoder, "", prog2, BigDecimal.ONE));
		LOG.info(prefix(nivaa) + String.join("", Collections.nCopies(BREDDE, "-")));
		LOG.info(prefix(nivaa) + String.format("%-21s : ", sluttKommentar) + andelBeregningLinje(partikoder, "", sammenslåttProg, BigDecimal.ONE));
		LOG.info(prefix(nivaa) + String.join("", Collections.nCopies(BREDDE, "-")));
	}

	public static void blankeKorrigert(Map<String, Kvotient> sluttPrognose, String nivaa, Set<String> partikoder) {
		BigDecimal blankeKorreksjon = blankeKorreksjon(sluttPrognose.get(Konstanter.BLANKE));
		String sluttKommentar = String.format("Blankekorrigert (faktor: %5.4f)", blankeKorreksjon);
		LOG.info(prefix(nivaa) + String.format("%-21s : ", "= Sluttprognose") + andelBeregningLinje(partikoder, sluttKommentar, sluttPrognose, blankeKorreksjon));
		LOG.info(prefix(nivaa) + String.join("", Collections.nCopies(BREDDE, "=")));
	}

	/**
	 * MSI mai 2015:
	 * Det BLANKE partiet regnes ikke med som underlag når man regner ut prosentvis oppslutning
	 * og/eller mandatfordeling. Det som hadde vært penest ville være å ikke ta inn
	 * blanke stemmer overhodet. Imidlertid har noen (media?) funnet ut at de ønsker å vite/vise
	 * hvor stor oppslutning BLANKE partiet har. Dermed må vi i prognose og rapporter
	 * beholde informasjonen om blanke stemmer, og korrigere for at disse ikke er en del av
	 * underlaget for oppslutning og for mandatfordeling. Den enkleste måten å ta de blanke
	 * stemmene ut av underlaget er ved å dele på en korreksjonsfaktor : (1 - prosent blanke)
	 * <p/>
	 * Dersom oppslutningen til et parti er f.eks. 48.31% og BLANKE-partiet har 3.38%, så
	 * gil 48.31%/(1-3.38%) gi 50.0%.
	 * <p/>
	 * Det finnes andre måter å komme frem til dette også, men dette er den korteste.
	 */
	private static BigDecimal blankeKorreksjon(Kvotient blankeAndel) {
		BigDecimal blankeKorreksjon = BigDecimal.ONE;
		if (blankeAndel != null) {
			blankeKorreksjon = blankeKorreksjon.subtract(blankeAndel.getKvotient());
		}
		return blankeKorreksjon;
	}

	public static void prognoseberegningInndata(Område område,
												Map<KommuneKretsParti, Integer> fok) {

		StringBuilder sb = new StringBuilder();
		sb.append("Inndata: ");

		Set<KommuneKrets> kretserFok = aggregerKretser(fok);
		int antBykretserFok = (int) kretserFok.stream().filter(sk -> Konstanter.isStorby(sk.getKommNr())).count();
		int stemmerFok = fok.values().stream().mapToInt(i -> i).sum();
		sb.append(String.format("FOK:%d/%d/%d ", kretserFok.size(), antBykretserFok, stemmerFok));
		
		sb.append(String.format("OpptaltFHS: %.1f%% ", HUNDRE * område.beregnOpptaltProsent(ForeløpigEndeligOppgjør.FORELØPIG, ValgtingForhand.F)));
		sb.append(String.format("OpptaltVTS: %.1f%% ", HUNDRE * område.beregnOpptaltProsent(ForeløpigEndeligOppgjør.FORELØPIG, ValgtingForhand.V)));
		sb.append(String.format("Opptalt-Tot: %.1f%% ", HUNDRE * område.beregnOpptaltProsent()));
		sb.append(String.format("Opptalt-For: %.1f%% ", HUNDRE * område.beregnOpptaltProsent(ForeløpigEndeligOppgjør.FORELØPIG, null)));
		Float frammøte = område.beregnFrammoteProsentIaar();
		if (frammøte == null) {
			sb.append("Frammøte: Kan ikke beregnes ennå");
		} else {
			sb.append(String.format("Frammøte: %.1f%%", HUNDRE * frammøte));
		}
		LOG.info(prefix(område.getNr()) + sb.toString());
	}

	private static Set<KommuneKrets> aggregerKretser(Map<KommuneKretsParti, Integer> stemmerPrKommuneKretsParti) {
		return stemmerPrKommuneKretsParti.keySet().stream().collect(Collectors.groupingBy(KommuneKretsParti::getKommuneKrets)).keySet();
	}


}


