package no.valg.valgnatt.backend.rapport;

import com.google.gson.JsonArray;
import no.valg.valgnatt.database.Konstanter;
import no.valg.valgnatt.database.domene.ValgType;
import no.valg.valgnatt.database.geografi.Fylke;
import no.valg.valgnatt.database.geografi.Land;
import no.valg.valgnatt.database.geografi.MandatData;
import no.valg.valgnatt.database.geografi.Mandater;
import no.valg.valgnatt.database.geografi.Område;
import no.valg.valgnatt.database.geografi.PartiMandater;
import no.valg.valgnatt.database.geografi.PartiStemmer;
import no.valg.valgnatt.database.geografi.Samevalgdistrikt;
import no.valg.valgnatt.database.hibernate.Kandidat;
import no.valg.valgnatt.database.hibernate.Stemmekrets;
import no.valg.valgnatt.database.hibernate.Valgdistrikt;

class FellesPartielement {

    private static final String ID = "id";
    private static final String PARTIKATEGORI = "partikategori";
    private static final String PARTIKODE = "partikode";
    private static final String NAVN = "navn";
    private static final String STEMMER = "stemmer";
    private static final String RESULTAT = "resultat";
    private static final String PROSENT = "prosent";
    private static final String ENDRING = "endring";
    private static final String SAMME = "samme";
    private static final String EKVIVALENT = "ekvivalent";
    private static final String ANTALL = "antall";
    private static final String TOTAL = "total";
    private static final String FHS = "fhs";
    private static final String PROGNOSE = "prognose";
    private static final String MANDATER = "mandater";
    private static final String UTJEVNING_ANTALL = "utjevningAntall";
    private static final String REPRESENTANTER = "representanter";
    private static final String KJONN = "kjonn";
    private static final String ALDER = "alder";
    private static final String UTJEVNINGSMANDAT = "utjevningsmandat";
    private static final String NESTE_KANDIDATER = "nesteKandidater";
    private static final int ANTALL_NESTE_KANDIDATER = 1;
    private static final int ÅR_2019 = 2019;
    private static final String KVOTIENT = "kvotient";
    private static final String MANDATRANG = "mandatrang";
    private static final String SISTE_MANDAT = "sisteMandat";
    private static final String NESTE_MANDAT = "nesteMandat";
    private static final String UTJEVNING = "utjevning";

    private FellesPartielement() {
    }
    
    static void leggTilParti(ValgType vt, PartiMandater totalPM, PartiMandater pm, PartiStemmer totalPS, PartiStemmer ps, JsonArray partier, Område område, int valgår) {
        JsonObjectWrapper parti = new JsonObjectWrapper();
        partier.add(parti.getJsonObject());
        leggTilPartiId(ps, parti);
        leggTilStemmer(totalPM, pm, totalPS, ps, parti, område);
        if (totalPM.getResultatData().getAntall(vt) > 0) {
            leggTilMandater(totalPM, parti, pm, område, valgår);
        }
    }

    private static void leggTilPartiId(PartiStemmer ps, JsonObjectWrapper parti) {
        JsonObjectWrapper partiId = parti.leggTilObjekt(ID);
        partiId.leggTilTall(PARTIKATEGORI, ps.getPartikategori());
        partiId.leggTilString(PARTIKODE, ps.getPartikode());
        partiId.leggTilString(NAVN, ps.getPartinavn());
    }

    private static void leggTilStemmer(PartiMandater totalPM, PartiMandater pm, PartiStemmer totalPS, PartiStemmer ps, JsonObjectWrapper parti, Område område) {
        JsonObjectWrapper partiStemmer = parti.leggTilObjekt(STEMMER);
        JsonObjectWrapper resultatStemmer = partiStemmer.leggTilObjekt(RESULTAT);
        leggTilProsent(totalPS, ps, område, resultatStemmer, ps.getStemmer(), totalPS.getStemmerBK());
        JsonObjectWrapper resultatStemmerAntall = resultatStemmer.leggTilObjekt(ANTALL);
        resultatStemmerAntall.leggTilTall(TOTAL, ps.getStemmer());
        resultatStemmerAntall.leggTilTall(FHS, ps.getFhs());

        MandatData totalPrognoseData = totalPM.getPrognoseData();
        if (totalPrognoseData.erPrognose()) {
            JsonObjectWrapper prognoseStemmer = partiStemmer.leggTilObjekt(PROGNOSE);
            MandatData prognoseData = pm.getPrognoseData();
            leggTilProsent(totalPS, ps, område, prognoseStemmer, prognoseData.getStemmer(), totalPrognoseData.getStemmerBK());
        }
    }

    private static void leggTilProsent(PartiStemmer totalPS, PartiStemmer ps, Område område, JsonObjectWrapper resultatStemmer, int stemmer, int stemmerBK) {
        resultatStemmer.leggTilTall(PROSENT, Format.pstNN5(stemmer, stemmerBK));
        JsonObjectWrapper resultatStemmerEndring = resultatStemmer.leggTilObjekt(ENDRING);
        if (område.getClass() != Stemmekrets.class && område.isSisteSammenlignbar()) {
            resultatStemmerEndring.leggTilTall(SAMME, Format.pstEndringNN5(stemmer, stemmerBK, ps.getNestsisteStemmer(), totalPS.getNestsisteStemmerBK()));
        } else {
            resultatStemmerEndring.leggTilTall(SAMME, (Number)null);
        }
        if (område.getClass() != Stemmekrets.class && område.isNestSisteSammenlignbar()) {
            resultatStemmerEndring.leggTilTall(EKVIVALENT, Format.pstEndringNN5(stemmer, stemmerBK, ps.getSisteStemmer(), totalPS.getSisteStemmerBK()));
        } else {
            resultatStemmerEndring.leggTilTall(EKVIVALENT, (Number)null);
        }   
    }

    private static void leggTilMandater(PartiMandater totalPM, JsonObjectWrapper parti, PartiMandater pm, Område område, int valgår) {
        JsonObjectWrapper partiMandater = parti.leggTilObjekt(MANDATER);
        leggTilMandaterPrognoseOgResultatObjekter(pm, område, pm.getResultatData(), partiMandater.leggTilObjekt(RESULTAT), valgår);

        if (totalPM.getPrognoseData().erPrognose()) {
            leggTilMandaterPrognoseOgResultatObjekter(pm, område, pm.finnBesteMandatData(true), partiMandater.leggTilObjekt(PROGNOSE), valgår);
        }
        
    }

    private static void leggTilMandaterPrognoseOgResultatObjekter(PartiMandater pm, Område område, MandatData md, JsonObjectWrapper forelder, int valgår) {
        leggTilMandaterUnderObjekt(forelder, område, md);

        if (område.getValgType() == ValgType.FY) {
            leggTilEkvivalent(forelder, md, område, valgår);
        }
        // I FY/KO-valg så endrer rekkefølgene av kandidatlistene seg pga lav sperregrense for renummerering + kumulering/stryking - dermed kan vi ikke vise kandidater
        if (erStortingsvalgOgFylke(område.getValgType(), område) || erSametingsvalgOgSamevalgdistrikt(område.getValgType(), område)) {
            leggTilKandidater(område.getValgType(), pm, område.getValgdistrikt(), forelder, md.erPrognose());
        }
    }

    private static boolean erStortingsvalgOgFylke(ValgType vt, Område område) {
        return vt == ValgType.ST && område.getClass() == Fylke.class;
    }

    private static boolean erSametingsvalgOgSamevalgdistrikt(ValgType vt, Område område) {
        return vt == ValgType.SA && område.getClass() == Samevalgdistrikt.class;
    }

    private static void leggTilEkvivalent(JsonObjectWrapper ekvivalentMandater, MandatData mandatData, Område område, int valgår) {
        JsonObjectWrapper resultatMandater = ekvivalentMandater.leggTilObjekt(EKVIVALENT);
        // På grunn av regionsreformen er ikke FY i 2019 sammenlignbart med ST i 2017, siden vi ikke lenger har 19 valgdistrikt. 
        if (ValgType.FY == område.getValgType() && ÅR_2019 == valgår) {
            resultatMandater.leggTilTall(ANTALL, (Number) null);
            resultatMandater.leggTilTall(ENDRING, (Number) null);
            resultatMandater.leggTilTall(UTJEVNING_ANTALL, (Number) null);
        } else {
            resultatMandater.leggTilTall(ANTALL, mandatData.getAntallMandaterGittSTValg());
            resultatMandater.leggTilTall(ENDRING, mandatData.getAntallMandaterGittSTValg() - mandatData.getSisteAntall());
            resultatMandater.leggTilTall(UTJEVNING_ANTALL, mandatData.getUtjevningmandater().getAntall());
        }
    }

    private static void leggTilMandaterUnderObjekt(JsonObjectWrapper prognoseEllerResultat, Område område, MandatData md) {
        prognoseEllerResultat.leggTilTall(ANTALL, md.getAntall(område.getValgType()));
        prognoseEllerResultat.leggTilTall(ENDRING, md.getAntall(område.getValgType()) - md.getNestsisteAntall());
        // Gir ikke så mye mening å vise aggregert siste mandat, altså viser vi bare dette dersom dette gjelder ett eneste valgdistrikt
        // Land-område har ett "fake-valgdistrikt" i FY/ST-valg, grunnet logikken omkring utjevningsmandat, så derfor må vi unnta dette området spesielt
        if (område.getValgdistrikter().size() == 1 && område.getClass() != Land.class) {
            leggTilSistemandat(md.getDistriktsmandater(), prognoseEllerResultat.leggTilObjekt(SISTE_MANDAT));
            leggTilNestemandat(md.getDistriktsmandater(), prognoseEllerResultat.leggTilObjekt(NESTE_MANDAT));
        }
        if (område.getValgType() == ValgType.ST) {
            prognoseEllerResultat.leggTilTall(UTJEVNING_ANTALL, md.getUtjevningmandater().getAntall());
            if (område.getClass() == Land.class) {
                leggTilSistemandat(md.getUtjevningmandater(), prognoseEllerResultat.leggTilObjekt(UTJEVNING));
            }
        } else {
            prognoseEllerResultat.leggTilTall(UTJEVNING_ANTALL, 0);
        }
    }

    private static void leggTilSistemandat(Mandater mandater, JsonObjectWrapper siste) {
        siste.leggTilTall(KVOTIENT, mandater.getLavesteKvotientMedMandat());
        siste.leggTilTall(MANDATRANG, mandater.getLavesteMandatrangMedMandat());
    }
    
    private static void leggTilNestemandat(Mandater mandater, JsonObjectWrapper neste) {
        neste.leggTilTall(KVOTIENT, mandater.getHøyesteKvotientUtenMandat());
        neste.leggTilTall(MANDATRANG, mandater.getHøyesteMandatrangUtenMandat());
    }

    private static void leggTilKandidater(ValgType vt, PartiMandater pm, Valgdistrikt valgdistrikt, JsonObjectWrapper forelder, boolean prognose) {
        JsonArray representanter = forelder.leggTilTabell(REPRESENTANTER);
        JsonArray nesteKandidater = forelder.leggTilTabell(NESTE_KANDIDATER);
        if (pm.getPartinavn().startsWith(Konstanter.ANDRE)) { // selv om ANDRE-gruppe får representanter, så er ikke dette et reelt parti
            return;
        }
        MandatData md = prognose ? pm.getPrognoseData() : pm.getResultatData();
        int antallRepresentanter = md.getAntall(vt);
        for (int i = 0; i < antallRepresentanter + ANTALL_NESTE_KANDIDATER; i++) {
            Kandidat kandidat = valgdistrikt.getPartiMandater(pm.getPartikode()).getKandidat(i + 1);
            if (i < antallRepresentanter && kandidat != null) { // Vi har ikke kandidater for gamle valg
                JsonObjectWrapper representantObjekt = lagRepresentantObjekt(kandidat);
                representantObjekt.leggTilBool(UTJEVNINGSMANDAT, i == md.getDistriktsmandater().getAntall() && md.getUtjevningmandater().getAntall() > 0);
                representanter.add(representantObjekt.getJsonObject());
            }
            else if (kandidat != null) {
                nesteKandidater.add(lagRepresentantObjekt(kandidat).getJsonObject());
            }
        }
    }
    
    private static JsonObjectWrapper lagRepresentantObjekt(Kandidat kandidat) {
        JsonObjectWrapper representantObjekt = new JsonObjectWrapper();
        representantObjekt.leggTilString(NAVN, kandidat.getNavn());
        representantObjekt.leggTilString(KJONN, kandidat.getKjonn().name());
        representantObjekt.leggTilTall(ALDER, kandidat.getAlder());
        return representantObjekt;
    }
}
