package no.valg.valgnatt.database.geografi;

import no.valg.valgnatt.database.domene.ValgType;
import no.valg.valgnatt.database.hibernate.Stemmekrets;
import no.valg.valgnatt.database.hibernate.Valgdistrikt;

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

/**
 * MSI des 2015:
 * Land er det øverste området for et valg, men arver selvsagt mange fellestrekk fra Område.
 */
public class Land extends Område {
	private ValgType valgType;
	private Map<String, Fylke> fylker = new TreeMap<>();
	private Map<String, Samevalgdistrikt> samevalgdistrikter = new TreeMap<>();
	// Disse brukes bare i ST/FY-valg og er sterkt knyttet til prognoseberegning og utjevningsmandater
	private Valgdistrikt valgdistrikt;

	public Land(ValgType valgType) {
		this.valgType = valgType;
	}

	@Override
	public String getNr() {
		return "0";
	}

	@Override
	public String getNavn() {
		return "norge";
	}

	@Override
	public String getRapportnavnURLEncLC() {
		return getRapportid();
	}

	@Override
	public String getRapportid() {
		return valgType.name().toLowerCase();
	}

	@Override
	public Område getOverordnetOmråde() {
		return null;
	}

	@Override
	public Område[] getUnderordnedeRapportOmråder() {
		if (valgType == ValgType.SA) {
			return samevalgdistrikter.values().toArray(new Område[]{});
		} else {
			return fylker.values().toArray(new Område[]{});
		}
	}

	@Override
	public ValgType getValgType() {
		return valgType;
	}

	public Samevalgdistrikt getSamevalgdistrikt(String nr) {
		return samevalgdistrikter.get(nr);
	}

	public Map<String, Samevalgdistrikt> getSamevalgdistrikter() {
		return samevalgdistrikter;
	}

	// Strata er ikke en del av den geografiske modell, dette er bare en datastruktur vi
	// bygger for å kunne regne prognose på dette området.
	public Strata lagStrata(String id, int minKretser, String... kommunenumre) {
		Strata strata = new Strata();
		strata.setLand(this);
		strata.setNr(id);
		strata.setMinimumKretserForPrognose(minKretser);
		for (String nr : kommunenumre) {
			Kommune kommune = getKommune(nr);
			if (kommune == null) {
				continue;
			}
			for (Stemmekrets s : kommune.getStemmekretser().values()) {
				strata.add(s, null, kommune.getNavn(), kommune.getValgdistrikt(), kommune.isSisteSammenlignbar(), kommune.isNestSisteSammenlignbar());
			}
		}
		return strata;
	}

	// Fungerer for alle valgtyper
	public Kommune getKommune(String kommnr) {
		return getKommuner().get(kommnr);
	}

	// Fungerer for alle valgtyper, og er litt raskere enn å gå veien om getStemmekretser()
	public Map<String, Kommune> getKommuner() {
		Map<String, Kommune> kommuneMap = new TreeMap<>();
		if (valgType == ValgType.SA) {
			for (Samevalgdistrikt samevalgdistrikt : samevalgdistrikter.values()) {
				kommuneMap.putAll(samevalgdistrikt.getKommuner());
			}
		} else { // valgType == FY/KO/ST
			for (Fylke fylke : fylker.values()) {
				kommuneMap.putAll(fylke.getKommuner());
			}
		}
		return kommuneMap;
	}

	public Fylke getFylke(String nr) {
		return fylker.get(nr);
	}

	// Fungerer for alle valgtyper
	@Override
	public Map<String, Stemmekrets> getStemmekretser() {
		Map<String, Stemmekrets> stemmekretsMap = new TreeMap<>();
		if (valgType == ValgType.SA) {
			for (Samevalgdistrikt samevalgdistrikt : samevalgdistrikter.values()) {
				stemmekretsMap.putAll(samevalgdistrikt.getStemmekretser());
			}
		} else { // valgType == FY/KO/ST
			for (Fylke fylke : fylker.values()) {
				stemmekretsMap.putAll(fylke.getStemmekretser());
			}
		}
		return stemmekretsMap;
	}

	// Bygger opp datamodellen
	public void add(Stemmekrets stemmekrets, Map<String, Bydel> bydeler, String kommNavn, Valgdistrikt valgdistrikt, boolean sisteSammenlignbar, boolean nestSisteSammenlignbar) {
		stemmekrets.setLand(this);
		if (valgType == ValgType.SA) {
			oppdaterDatamodellForSA(stemmekrets, kommNavn, valgdistrikt, sisteSammenlignbar, nestSisteSammenlignbar);
		} else {
			oppdaterDatamodellForSTFYKO(valgType, stemmekrets, bydeler, kommNavn, valgdistrikt, sisteSammenlignbar, nestSisteSammenlignbar);
		}
	}

	private void oppdaterDatamodellForSTFYKO(ValgType valgType, Stemmekrets stemmekrets, Map<String, Bydel> bydeler, String kommNavn, Valgdistrikt valgdistrikt, boolean sisteSammenlignbar, boolean nestSisteSammenlignbar) {
		String fylkenr = stemmekrets.getId().getKommnr().substring(0, 2);

		Fylke fylke = fylker.get(fylkenr);
		if (fylke == null) {
			fylke = new Fylke();
			fylke.setNr(fylkenr);
			fylke.setLand(this);
			fylker.put(fylkenr, fylke);
		}
		fylke.add(stemmekrets, bydeler, kommNavn, valgdistrikt, sisteSammenlignbar, nestSisteSammenlignbar);
		if (valgdistrikt != null && (valgType == ValgType.FY || valgType == ValgType.ST)) {
			fylke.setValgdistrikt(valgdistrikt);
			fylke.setNavn(valgdistrikt.getNavn());
		} else {
			// fylkenavnet hentes fra en statisk map i Fylke-objektet i tilfellet KO-valg eller når vi har data fra før 2009
			// valgdistriktet vil mangle i geografier før 2009
		}
	}

	private void oppdaterDatamodellForSA(Stemmekrets stemmekrets, String kommNavn, Valgdistrikt valgdistrikt, boolean sisteSammenlignbar, boolean nestSisteSammenlignbar) {
		if (kommNavn == null || valgdistrikt == null) {
			// MSI okt 2015:
			// Får ikke oppdatert samevalgdistriktene - dette skjer dersom valgåret er 2009 eller eldre,
			// fordi vi ikke har kommunestrukturene lagt opp i grunnlagsdata.
			// Så langt jeg nå vet, så trenger vi ikke samevalgdistriktene fra før 2011.
			return;
		}
		String samevalgdistriktsnr = "" + valgdistrikt.getId().getNr();

		Samevalgdistrikt samevalgdistrikt = samevalgdistrikter.computeIfAbsent(samevalgdistriktsnr, k -> new Samevalgdistrikt());
		samevalgdistrikt.setLand(this);
		samevalgdistrikt.setValgdistrikt(valgdistrikt);
		samevalgdistrikt.setNr(samevalgdistriktsnr);
		samevalgdistrikt.setNavn(valgdistrikt.getNavn());
		samevalgdistrikt.add(stemmekrets, null, kommNavn, valgdistrikt, sisteSammenlignbar, nestSisteSammenlignbar);
	}

	/**
	 * MSI des 2015:
	 * Et valgdistrikt må opprettes på landsnivå av følgende 3 grunner:
	 * 1. Beregningen av utjevningsmandater skjer på landsnivå (man kan tenke på utjevningsmandat-valget som et landsvalg med 19 mandater - 1 for hvert fylke).
	 * 2. Prognoseberegningen av landsresultat skjer på bakgrunn av en samlet beregning av alle stemmeresultater i hele landet, ikke som en aggregert resultat
	 * av tallene fra hvert fylke.
	 * 3. Landet har ingen stemmekrets knyttet til seg, slik at knytningen til den geografiske modellen ikke skjer av seg selv i gjennomgangen av stemmekretser,
	 * følgelig må den skje "manuelt" her.
	 * <p/>
	 * Legger til at dersom man har et FY-valg, så ønskes det fortsatt å opprettes et valgdistrikt for utjevningsmandater og landsprognose, fordi man regner
	 * ut dette i FY-valg også, dvs. regner ut *som om* det var ST-valg, for å kunne vise hvordan ST-valget ville gått.
	 */
	public void setValgdistriktForUtjevningsmandaterOgLandsprognose(Valgdistrikt vd) {
		valgdistrikt = vd;
	}


    /* I resten av filen har vi sære og spesielle ting som må gjøres med ST/FY-valg */

	@Override
	public Valgdistrikt getValgdistrikt() {
		return valgdistrikt; // Har bare innhold i ST/FY-valg
	}

	@Override
	public Map<String, Valgdistrikt> getValgdistrikter() {
		if (valgType == ValgType.ST || valgType == ValgType.FY) {
			Map<String, Valgdistrikt> map = new TreeMap<>();
			map.put(valgdistrikt.getId().getNr(), valgdistrikt);
			return map;
		}
		return super.getValgdistrikter();
	}

	@Override
	public int getAntallMandater() {
		if (valgType == ValgType.ST) {
			return getFylker().values().stream().mapToInt(f -> f.getValgdistrikt().getAntallmandater()).sum() + valgdistrikt.getAntallmandater();
		}
		if (valgType == ValgType.FY) {
			return getFylker().values().stream().mapToInt(f -> f.getValgdistrikt().getAntallmandater()).sum();
		}
		return super.getAntallMandater();
	}

	public Map<String, Fylke> getFylker() {
		return fylker;
	}

}
