package no.valg.valgnatt.database.forberedvalg.geografi;

import no.valg.valgnatt.database.Konstanter;
import no.valg.valgnatt.database.domene.ValgType;
import no.valg.valgnatt.database.forberedvalg.bean.BeanHistorikkStemmetall;
import no.valg.valgnatt.database.forberedvalg.konvertering.KonverteringStrategi;
import no.valg.valgnatt.database.forberedvalg.konvertering.Konverteringer;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;

import static no.valg.valgnatt.database.domene.ValgType.KO;
import static no.valg.valgnatt.database.domene.ValgType.SA;

public class Valg {
    private static final Logger LOG = Logger.getLogger("console");
    private static final int FIRE_ÅR = 4;
    private static final int TO_ÅR = 2;
    private static final int KOMM_START_POS = 0;
    private static final int KOMM_STOPP_POS = 4;
    private static final int KRETS_START_POS = 5;
    private static final int KRETS_STOPP_POS = 9;
    private static final int ÅR_2009 = 2009;
    private static final int ÅR_2017 = 2017;

    static {
        System.setProperty("java.util.logging.SimpleFormatter.format", "%5$s\n");
    }


    // Nøkler
    private ValgType valgType;
    private int valgår;

    // Data
    private Map<String, Fylke> fylkeMap = new TreeMap<>();


    // Peker tilbake til geohierarki (for å kunne tilby sisteValg() og nestsisteValg()
    private AlleValg alleValg;

    public Valg(ValgType valgType, int valgår, AlleValg alleValg) {
        this.valgType = valgType;
        this.valgår = valgår;
        this.alleValg = alleValg;
    }

    public ValgType getValgType() {
        return valgType;
    }

    public int getValgår() {
        return valgår;
    }

    // Slår opp på en krets i dette valget - gjør ingen konverteringer!!
    public Krets getKrets(String kommnr, String kretsnr) {
        List<Krets> kretser = getKretser(kommnr, kretsnr, 0, KonverteringStrategi.NA);
        return kretser.isEmpty() ? null : kretser.get(0);
    }

    // Metoden må håndtere at vi må konvertere ny->gammel og gammel->ny kretsstruktur, derav får vi mulighet for flere kretser i retur!
    public List<Krets> getKretser(String kommnr, String kretsnr, int år, KonverteringStrategi konverteringStrategi) {
        Fylke fylke = fylkeMap.get(kommnr.substring(0, 2));
        List<Krets> kretser = new ArrayList<>();
        if (fylke != null) {
            Kommune kommune = fylke.getKommune(kommnr);
            if (kommune != null && kretsnr != null) {
                Krets k = kommune.getKrets(kretsnr);
                if (k != null) {
                    kretser.add(k);
                }
            }
        }
        if (kretser.isEmpty()) { // Fant ingen match - vi kan forsøke en konvertering for å se om det har blitt endret fylkenr/kommnr/kretsnr
            List<String> konvertertKretser = Konverteringer.krets(kommnr, kretsnr, år, konverteringStrategi);
            for (String konvertertKrets : konvertertKretser) {
                Krets k = getKrets(konvertertKrets.substring(KOMM_START_POS, KOMM_STOPP_POS), konvertertKrets.substring(KRETS_START_POS, KRETS_STOPP_POS));
                if (k != null) {
                    kretser.add(k);
                }
            }
        }
        return kretser;
    }

    public boolean finnesKrets(Krets krets, int år, KonverteringStrategi konverteringStrategi) {
        return getKrets(krets.getKommnr(), krets.getKretsnr()) != null || !Konverteringer.krets(krets.getKommnr(), krets.getKretsnr(), år, konverteringStrategi).isEmpty();
    }

    private boolean finnesParti(Krets krets, Parti parti, int år, KonverteringStrategi konverteringStrategi) {
        List<Krets> kretser = getKretser(krets.getKommnr(), krets.getKretsnr(), år, konverteringStrategi);
        for (Krets k : kretser) {
            if (k.getParti(parti.getPartikode()) != null) {
                return true;
            }
        }
        return false;
    }

    public void leggTilKrets(Krets krets) {
        // Dette er ikke korrekt for SA-valg i 2017 og utover, men det gjør ikke noe fordi vi ikke kommer til å hente ut alle SA-kommuner i et
        // bestemt SA-fylke Oppdelingen i fylker er således noe vi gjør mest for å kunne navigere litt enklere i datastrukturen når man debugger f.eks.
        if (fylkeMap.get(krets.getKommnr().substring(0, 2)) == null) {
            fylkeMap.put(krets.getKommnr().substring(0, 2), new Fylke());
        }
        fylkeMap.get(krets.getKommnr().substring(0, 2)).leggTilKrets(krets);
    }

    /**
     * Vi returnerer et valg for to år siden. Dersom det finnes et valg av samme type (dette er isåfall et ekstraordinært tilfelle)
     * så returneres det. Hvis ikke returneres ekvivalent-valget.
     *
     * Det var et ekstraordinært kommunestyrevalg i 2017, da Tjøme og Nøtterøy ble slått sammen til Færder kommune, som vi ikke ønsker å retrunere når man spør etter valg i 2017. 
     */
    public Valg getSisteValg() {
        Valg valg = alleValg.getValg(valgType, valgår - TO_ÅR);
        if (valg != null && !kommunestyrevalgFærder2017(valg)) {
            return valg;
        } else {
            return alleValg.getValg(valgType.getEkvivalent(), valgår - TO_ÅR);
        }
    }

    private boolean kommunestyrevalgFærder2017(Valg valg) {
        return KO.equals(valg.getValgType()) && ÅR_2017 == valg.getValgår();
    }

    /**
     * Vi returnerer et valg for fire år siden. Dersom det finnes et valg av samme type så returneres det. Hvis ikke
     * returneres ekvivalent-valget (dette er i såfall et ekstraordinært tilfelle).
     */
    public Valg getNestSisteValg() {
        Valg valg = alleValg.getValg(valgType, valgår - FIRE_ÅR);
        if (valg != null) {
            return valg;
        } else {
            return alleValg.getValg(valgType.getEkvivalent(), valgår - FIRE_ÅR);
        }
    }

    // Sjekk de to foregående valg - dersom det finnes kretser der som ikke er i årets valg, så må vi ta med de stemmeberettigede fra disse kretsene og legge dem på 0000-kretsen
    // Dette gjøres fordi vi ikke vil miste noen stemmer fra tidligere valg når vi gjør sammenligninger på frammøteprosent i rapportene
    public void oppdaterSbForUtgåtteKretser() {
        if (this.valgType != SA) { // Finnes ikke noe siste-valg for SA, bare nest-siste
            flyttSbTil0000KretsITidligereValgDersomKretsErUtgått(this, this.getSisteValg());
        }
        flyttSbTil0000KretsITidligereValgDersomKretsErUtgått(this, this.getNestSisteValg());
    }

    // dv = DetteValget, tv = TidligereValg
    private void flyttSbTil0000KretsITidligereValgDersomKretsErUtgått(Valg dv, Valg tv) {
        for (Krets kretsTV : tv.getKretser()) {
            if (!dv.finnesKrets(kretsTV, dv.valgår, KonverteringStrategi.GAMMEL_TIL_NY)) {
                // Kretsen fantes i et TV, men mangler i DV - vi tar sb fra denne kretsen og legger til på 0000-kretsen i denne kommunen
                // Legg merke til at vi ikke endrer DV, men TV - dette skal leses ut på et senere tidspunkt
                tv.getKrets(kretsTV.getKommnr(), Konstanter.DEFAULT_KRETS).leggTilSb(kretsTV.getSb());
            }
        }
    }

    public void kopierStemmerTil0000KretsenDersomKretsenIkkeFinnesITidligereValg() {
        if (this.valgType != SA) { // Finnes ikke noe siste-valg for SA, bare nest-siste
            flyttStemmerTil0000KretsITidligereValgDersomKretsErUtgått(this, this.getSisteValg());
        }
        flyttStemmerTil0000KretsITidligereValgDersomKretsErUtgått(this, this.getNestSisteValg());
    }

    // dv = DetteValget, tv = TidligereValg
    private void flyttStemmerTil0000KretsITidligereValgDersomKretsErUtgått(Valg dv, Valg tv) {
        for (Krets kretsTV : tv.getKretser()) {
            if (!dv.finnesKrets(kretsTV, dv.valgår, KonverteringStrategi.GAMMEL_TIL_NY)) {
                // Kretsen fantes i TV, men mangler i DV (selv med forsøk på konvertering) - vi tar fhs/vts/mandater/utj.mandater fra alle partiene i
                // denne kretsen og legger dem til de tilsvarende partiene i 0000-kretsen i denne kommunen. Legg merke til at vi ikke
                // endrer DV, men TV - dette skal leses ut på et senere tidspunkt
                for (Parti parti : kretsTV.getPartier()) {
                    Parti partiIDefaultKrets = tv.getKrets(kretsTV.getKommnr(), Konstanter.DEFAULT_KRETS).getParti(parti.getPartikode());
                    if (partiIDefaultKrets == null) { // Kan skje dersom et parti hadde 0 stemmer i forrige valg i default krets
                        Krets defaultKrets = tv.getKrets(kretsTV.getKommnr(), Konstanter.DEFAULT_KRETS);
                        partiIDefaultKrets = new Parti(defaultKrets, parti.getPartikode(), 0, 0, null, null);
                        defaultKrets.leggTilParti(partiIDefaultKrets);
                    }
                    partiIDefaultKrets.leggTilStemmer(parti.getFhs(), parti.getVts());
                    partiIDefaultKrets.leggTilMandater(parti.getMnd(), parti.getUmnd());
                }
            }
        }
    }

    public void opprettPartierSomBareStilteITidligereValg() {
        if (this.valgType != SA) { // Finnes ikke noe siste-valg for SA, bare nest-siste
            opprettPartierSomBareStilteITidligereValg(this, this.getSisteValg());
        }
        opprettPartierSomBareStilteITidligereValg(this, this.getNestSisteValg());
    }

    // dv = DetteValget, tv = TidligereValg
    private void opprettPartierSomBareStilteITidligereValg(Valg dv, Valg tv) {
        // DV = DetteValget, TV = TidligereValg
        for (Krets kretsTV : tv.getKretser()) {
            List<Krets> kretserDV = dv.getKretser(kretsTV.getKommnr(), kretsTV.getKretsnr(), dv.valgår, KonverteringStrategi.GAMMEL_TIL_NY);
            // Vi sjekker bare kretser som finnes i begge valg, fordi alle stemmer har blitt flyttet (se flyttStemmerTil0000KretsITidligereValgDersomKretsErUtgått())
            for (Krets kretsDV : kretserDV) {
                for (Parti partiTV : kretsTV.getPartier()) {
                    if (!dv.finnesParti(kretsTV, partiTV, dv.valgår, KonverteringStrategi.GAMMEL_TIL_NY)) {
                        kretsDV.leggTilPartiSomIkkeStiller(partiTV.getPartikode());
                    }
                }
            }
        }
    }

    public void leggTilPartierIHverKrets(Map<String, List<String>> partikoderPrValgdistrikt) {
        for (Krets krets : this.getKretser()) {
            List<String> partikoder = partikoderPrValgdistrikt.get(krets.getValgdistriktnr());
            for (String partikode : partikoder) {
                krets.leggTilParti(new Parti(krets, partikode, 0, 0, 0, 0));
            }
        }
    }

    public List<Krets> getKretser() {
        List<Krets> kretser = new ArrayList<>();
        for (Fylke fylke : fylkeMap.values()) {
            for (Kommune kommune : fylke.getKommuner()) {
                kretser.addAll(kommune.getKretser());
            }
        }
        return kretser;
    }

    public Map<String, Map<String, Parti>> getPartierPrValgdistrikt() {
        Map<String, Map<String, Parti>> partierPrValgdistrikt = new TreeMap<>();
        for (Krets krets : this.getKretser()) {
            Map<String, Parti> partier = partierPrValgdistrikt.computeIfAbsent(krets.getValgdistriktnr(), k -> new TreeMap<>());
            for (Parti parti : krets.getPartier()) {
                partier.put(parti.getPartikode(), parti);
            }
        }
        return partierPrValgdistrikt;
    }

    public String toString() {
        return valgType + "-" + valgår;
    }

    public void leggTilStemmetall(Map<String, BeanHistorikkStemmetall> historikkStemmetallMap) {
        for (BeanHistorikkStemmetall hs : historikkStemmetallMap.values()) {
            if (hs.getValgaar() != valgår || !hs.getValgtype().equals(valgType.name())) {
                continue;
            }
            Krets krets = getKommune(hs.getKommnr()).getKrets(hs.getKretsnr());
            if (krets == null) {
                LOG.severe(() -> "Krets mangler for " + hs.getKommnr() + "-" + hs.getKretsnr() + " i " + valgType + "-" + valgår + ", får ikke lagt til stemmer");
            } else {
                Parti parti = krets.getParti(hs.getPartikode());
                parti.leggTilStemmer(hs.getFhs(), hs.getVts());
            }
        }
    }

    public Kommune getKommune(String kommnr) {
        if (fylkeMap.get(kommnr.substring(0, 2)) == null && valgType == SA && valgår == ÅR_2009) {
            // Et spesialtilfelle som ikke har noen betydning for oppbygging av historikk - dvs. historikken for 2013-valget kunne blitt bedre,
            // men det er ikke noe vi jobber mer med. Problemet er at det finnes mange SA-kommuner registrert i 2009, men bare 7 stemmekretser.
            return null;
        }
        // Vi forventer alltid å finne kommunen på dette tidspunktet - dvs. at fylkeMap/kommMap allerede har blitt populert
        return fylkeMap.get(kommnr.substring(0, 2)).getKommune(kommnr);
    }
}
