package no.valg.valgnatt.backend.rapport;

import com.google.gson.JsonArray;
import no.valg.valgnatt.database.domene.ForeløpigEndeligOppgjør;
import no.valg.valgnatt.database.domene.ValgType;
import no.valg.valgnatt.database.domene.ValgtingForhand;
import no.valg.valgnatt.database.geografi.Land;
import no.valg.valgnatt.database.geografi.Område;
import no.valg.valgnatt.database.geografi.PartiMandater;
import no.valg.valgnatt.database.geografi.PartiStemmer;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;

/**
 * MSI jan 2016:
 * ResultatRapport gir all nødvendig informasjon om et område (som media og andre konsumenter av API'et trenger)
 */
class TotalRapport {
    private static final int RAPPORTNAVN_RAPPORTTYPE_INDEX = 2;
    private static final int RAPPORTNAVN_NIVAA_INDEX = 3;
    private static final String STEMMER = "stemmer";
    private static final String PARTIER = "partier";
    private static final String OPPTALT = "opptalt";
    private static final String FORELOPIG_FHS = "forelopigFhs";
    private static final String FORELOPIG_VTS = "forelopigVts";
    private static final String FORELOPIG = "forelopig";
    private static final String ENDELIG = "endelig";
    private static final String OPPGJOR = "oppgjor";
    private static final String LAND = "land";
    private static final String FYLKE = "fylke";
    private static final String KOMMUNE = "kommune";
    private static final String STEMMEKRETS = "stemmekrets";
    private static final String GEOGRAFI = "geografi";
    private static final String ANTALL_STEMMEBERETTIGEDE = "antallStemmeberettigede";
    private static final String STEMMEGIVNINGER = "stemmegivninger";
    private static final String BLANKE = "blanke";
    private static final String SAMEVALGDISTRIKT = "samevalgdistrikt";
    private static final String FYLKE_NR = "fylkeNr";
    private static final String FYLKE_NAVN = "fylkeNavn";
    private static final String KOMMUNE_NR = "kommuneNr";
    private static final String KOMMUNE_NAVN = "kommuneNavn";
    private static final String KRETS_NR = "kretsNr";
    private static final String KRETS_NAVN = "kretsNavn";
    private static final String GODKJENTE = "godkjente";
    private static final String FORKASTEDE = "forkastede";
    private static final String FORHÅND = "forhånd";
    private static final String VALGTING = "valgting";
    private static final String TOTALT = "totalt";

    private TotalRapport() {
    }


    //    totalrapport?vt=<valgtype>&aar=<valgår>&type=<stemmer|partier&nivaa=land|fylke|kommune|stemmekrets>
    //    /st/02/0229
    //    /ujm
    //    /totalrapport/st/partier/krets

    static String lagRapport(String rapportNavn, int valgÅr, Land land, Map<String, MainRapport.RappCacheEntry> rapporterNavnMap) {

        String rapporttype = finnRapportype(rapportNavn);
        String nivå = finnNivaa(rapportNavn);
        Collection<? extends Område> områder = finnOmråder(land, nivå);

        JsonObjectWrapper rapport = new JsonObjectWrapper();

        FellesRapportelementer.leggTilId(valgÅr, land.getValgType(), nivå, rapporttype, land.getNr(), rapport);
        FellesRapportelementer.leggTilTidspunkt(land, rapport, rapporterNavnMap.isEmpty());
        leggTilOpptaltProsent(land, rapport);

        if (STEMMER.equals(rapporttype)) {
            return lagStemmerapport(områder, nivå, rapport);
        } else if (PARTIER.equals(rapporttype)) {
            return lagPartirapport(områder, nivå, rapport, valgÅr);
        }

        return null;
    }

    private static void leggTilOpptaltProsent(Land land, JsonObjectWrapper rapport) {
        JsonObjectWrapper opptalt = rapport.leggTilObjekt(OPPTALT);
        opptalt.leggTilTall(FORELOPIG_FHS, Format.pst(land.beregnOpptaltProsent(ForeløpigEndeligOppgjør.FORELØPIG, ValgtingForhand.F)));
        opptalt.leggTilTall(FORELOPIG_VTS, Format.pst(land.beregnOpptaltProsent(ForeløpigEndeligOppgjør.FORELØPIG, ValgtingForhand.V)));
        opptalt.leggTilTall(FORELOPIG, Format.pst(land.beregnOpptaltProsent(ForeløpigEndeligOppgjør.FORELØPIG, null)));
        opptalt.leggTilTall(ENDELIG, Format.pst(land.beregnOpptaltProsent(ForeløpigEndeligOppgjør.ENDELIG, null)));
        opptalt.leggTilTall(OPPGJOR, Format.pst(land.beregnOpptaltProsent(ForeløpigEndeligOppgjør.OPPGJØR, null)));
    }

    private static String finnRapportype(String rapportNavn) {
        return rapportNavn.split("/")[RAPPORTNAVN_RAPPORTTYPE_INDEX];
    }

    private static String finnNivaa(String rapportNavn) {
        return rapportNavn.split("/")[RAPPORTNAVN_NIVAA_INDEX];
    }

    private static Collection<? extends Område> finnOmråder(Land land, String nivå) {
        switch (nivå) {
            case LAND:
            return Collections.singletonList(land);
            case FYLKE:
            return land.getValgType() == ValgType.SA ? land.getSamevalgdistrikter().values() : land.getFylker().values();
            case KOMMUNE:
            return land.getKommuner().values();
            case STEMMEKRETS:
            return land.getStemmekretser().values();
        default:
            return Collections.emptyList();
        }
    }

    private static String lagStemmerapport(Collection<? extends Område> områder, String nivå, JsonObjectWrapper rapport) {
        JsonArray geografi = rapport.leggTilTabell(GEOGRAFI);
        for (Område område : områder) {
            JsonObjectWrapper tabellrad = new JsonObjectWrapper();
            geografi.add(tabellrad.getJsonObject());

            leggTilGeografi(nivå, område, tabellrad);

            tabellrad.leggTilTall(ANTALL_STEMMEBERETTIGEDE, område.getAntallStemmeberettigede());

            JsonObjectWrapper stemmegivninger = tabellrad.leggTilObjekt(STEMMEGIVNINGER);
            leggTilGodkjentOgForkastede(stemmegivninger, område.getAntallStgFhsGodkjente(), område.getAntallStgGodkjente(), område.getAntallStgFhsForkastede(), område.getAntallStgForkastede());

            JsonObjectWrapper stemmesedler = tabellrad.leggTilObjekt(STEMMER);
            PartiStemmer partiStemmer = område.sumStemmerTotal();
            leggTilGodkjentOgForkastede(stemmesedler, partiStemmer.getFhs(), partiStemmer.getStemmer(), område.getAntallStsFhsForkastede(), område.getAntallStsForkastede());

            JsonObjectWrapper blanke = stemmesedler.leggTilObjekt(BLANKE);
            leggTilTall(blanke, getFhsBlanke(partiStemmer), getBlanke(partiStemmer));
        }

        return rapport.getJsonObject().toString();
    }

    private static void leggTilGeografi(String nivå, Område område, JsonObjectWrapper innslag) {
        if (harFylkesnivå(nivå)) {
            Område landinndeling = finnOmråde(område, område.getValgType() == ValgType.SA ? SAMEVALGDISTRIKT : FYLKE);
            innslag.leggTilString(FYLKE_NR, landinndeling.getNr());
            innslag.leggTilString(FYLKE_NAVN, landinndeling.getNavn());
        }
        if (harKommunenivå(nivå)) {
            Område kommune = finnOmråde(område, KOMMUNE);
            innslag.leggTilString(KOMMUNE_NR, kommune.getNr());
            innslag.leggTilString(KOMMUNE_NAVN, kommune.getNavn());
        }
        if (harKretsnivå(nivå)) {
            Område krets = finnOmråde(område, STEMMEKRETS);
            innslag.leggTilString(KRETS_NR, krets.getNr());
            innslag.leggTilString(KRETS_NAVN, krets.getNavn());
        }
    }

    private static boolean harFylkesnivå(String nivå) {
        return !LAND.equals(nivå);
    }

    private static boolean harKommunenivå(String nivå) {
        return KOMMUNE.equals(nivå) || STEMMEKRETS.equals(nivå);
    }

    private static boolean harKretsnivå(String nivå) {
        return STEMMEKRETS.equals(nivå);
    }

    private static Område finnOmråde(Område område, String ønsketNivå) {
        Område o = område;
        String nivå = getNivåNavn(o);
        while (!ønsketNivå.equals(nivå)) {
            o = o.getOverordnetOmråde();
            nivå = getNivåNavn(o);
        }
        return o;
    }

    private static String getNivåNavn(Område o) {
        return o.getClass().getSimpleName().toLowerCase();
    }

    private static void leggTilGodkjentOgForkastede(JsonObjectWrapper stemmegivninger, int antallFhsGodkjente, int antallGodkjente, int antallFhsForkastede, int antallForkastede) {
        JsonObjectWrapper stgGodkjente = stemmegivninger.leggTilObjekt(GODKJENTE);
        leggTilTall(stgGodkjente, antallFhsGodkjente, antallGodkjente);
        JsonObjectWrapper stgForkastede = stemmegivninger.leggTilObjekt(FORKASTEDE);
        leggTilTall(stgForkastede, antallFhsForkastede, antallForkastede);
    }

    private static void leggTilTall(JsonObjectWrapper jsonObjectWrapper, int forhånd, int totalt) {
        jsonObjectWrapper.leggTilTall(FORHÅND, forhånd);
        jsonObjectWrapper.leggTilTall(VALGTING, totalt - forhånd);
        jsonObjectWrapper.leggTilTall(TOTALT, totalt);
    }

    private static int getFhsBlanke(PartiStemmer partiStemmer) {
        return partiStemmer.getFhs() - partiStemmer.getStemmerFhsBK();
    }

    private static int getBlanke(PartiStemmer partiStemmer) {
        return partiStemmer.getStemmer() - partiStemmer.getStemmerBK();
    }

    private static String lagPartirapport(Collection<? extends Område> områder, String nivå, JsonObjectWrapper rapport, int valgår) {
        JsonArray geografitabell = rapport.leggTilTabell(GEOGRAFI);
        for (Område område : områder) {
            JsonObjectWrapper geografitabellrad = new JsonObjectWrapper();
            geografitabell.add(geografitabellrad.getJsonObject());

            leggTilGeografi(nivå, område, geografitabellrad);

            geografitabellrad.leggTilTall(ANTALL_STEMMEBERETTIGEDE, område.getAntallStemmeberettigede());
            JsonArray partier = geografitabellrad.leggTilTabell(PARTIER);
            ValgType valgType = område.getValgType();
            PartiMandater totalPartiMandater = område.sumMandaterTotal();
            PartiStemmer totalPartiStemmer = område.sumStemmerTotal();
            for (PartiStemmer partiStemmer : område.aggregerStemmerPrParti(Område.Summering.PARTIER_SOM_STILLER)) {
                FellesPartielement.leggTilParti(valgType, totalPartiMandater, område.sumMandaterParti(partiStemmer.getPartikode()), totalPartiStemmer, partiStemmer, partier, område, valgår);
            }
        }

        return rapport.getJsonObject().toString();
    }
}
