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.Fylke;
import no.valg.valgnatt.database.geografi.Kommune;
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 no.valg.valgnatt.database.hibernate.Stemmekrets;

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 ResultatRapport {

    private static final int FYLKE_ELLER_SAMEVALGDISTRIKT = 2;
    private static final int KOMMUNE = 3;
    private static final int BYDEL_ELLER_STEMMEKRETS = 4;
    private static final int KRETSNUMMER_SIFRE = 4;
    private static final int TIDSPUNKT_OFFSET_START = 18;
    private static final int TIDSPUNKT_STR_LENGDE = 19;

    private static final int HUNDRE = 100;
    private static final int FEMTEN = 15;
    private static final double SJUOGENHALV = 7.5;

    private static final String PARTIER = "partier";
    private static final String MANDATER = "mandater";
    private static final String ANTALL = "antall";
    private static final String ENDRING = "endring";
    private static final String STEMMER = "stemmer";
    private static final String TOTAL = "total";
    private static final String FHS = "fhs";
    private static final String TOTAL_FORKASTEDE = "totalForkastede";
    private static final String FHS_FORKASTEDE = "fhsForkastede";
    private static final String STEMMEGIVING = "stemmegiving";
    private static final String TOTAL_GODKJENTE = "totalGodkjente";
    private static final String FHS_GODKJENTE = "fhsGodkjente";
    private static final String FRAMMOTE = "frammote";
    private static final String PROSENT = "prosent";
    private static final String PROSENT_STEMMEGIVING = "prosentStemmegiving";
    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 PROGNOSE = "prognose";
    private static final String BEREGNET = "beregnet";
    private static final String HISTORISK_TOTALAVVIK = "historiskTotalavvik";
    private static final String LINKS = "_links";
    private static final String RELATED = "related";
    private static final String NR = "nr";
    private static final String HREF = "href";
    private static final String HREF_NAVN = "hrefNavn";
    private static final String NAVN = "navn";
    private static final String RAPPORT_GENERERT = "rapportGenerert";
    private static final String HAR_UNDERORDNET = "harUnderordnet";
    private static final String SELF = "self";
    private static final String MERKNADER = "merknader";

    private ResultatRapport() {
    }

    static String lagRapport(String rapportNavn, int valgÅr, Land land, Map<String, MainRapport.RappCacheEntry> rapporterNavnMap) {
        ValgType vt = ValgType.valueOf(rapportNavn.split("/")[0].toUpperCase());
        Område område = finnOmråde(rapportNavn, vt, land);

        JsonObjectWrapper rapp = new JsonObjectWrapper();

        String nivå = område.getClass().getSimpleName().toLowerCase();
        FellesRapportelementer.leggTilId(valgÅr, vt, nivå, område.getNavn(), område.getNr(), rapp);
        FellesRapportelementer.leggTilTidspunkt(område, rapp, rapporterNavnMap.isEmpty());
        leggTilMerknader(område, valgÅr, rapp);
        rapp.leggTilTall("antallsb", område.getAntallStemmeberettigede());
        PartiMandater totalPM = område.sumMandaterTotal();
        leggTilMandaterAggregert(rapp, område, totalPM);
        PartiStemmer totalPS = område.sumStemmerTotal();
        leggTilStemmer(område, rapp, totalPS);
        leggTilFrammøte(område, rapp);
        leggTilOpptalt(område, rapp);
        leggTilPrognoseFlagg(rapp, totalPM, vt, område);

        JsonArray partier = rapp.leggTilTabell(PARTIER);
        for (PartiStemmer ps : område.aggregerStemmerPrParti(Område.Summering.PARTIER_SOM_STILLER)) {
            FellesPartielement.leggTilParti(vt, totalPM, område.sumMandaterParti(ps.getPartikode()), totalPS, ps, partier, område, valgÅr);
        }
        FellesPartielement.leggTilParti(vt, totalPM, område.sumMandaterAndre(), totalPS, område.sumStemmerAndre(), partier, område, valgÅr);
        FellesPartielement.leggTilParti(vt, totalPM, område.sumMandaterAndre2(totalPM), totalPS, område.sumStemmerAndre2(), partier, område, valgÅr);
        leggTilLenker(område, rapp, valgÅr, rapporterNavnMap);

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

    /**
     * Gyldige rapportNavn er:
     * st|fy|ko[/<fylkenr>[/<kommnr>[/<kretsnr>]]]
     * st|fy|ko[/<fylkenr>[/<kommnr>[/<bydelsnr>]]]
     * sa[/<samevalgdistriktnr>]
     */
    private static Område finnOmråde(String rapportNavn, ValgType vt, Land land) {
        String[] sti = rapportNavn.split("/");
        Område område = land;
        if (sti.length >= FYLKE_ELLER_SAMEVALGDISTRIKT) {
            if (vt == ValgType.SA) {
                område = ((Land) område).getSamevalgdistrikt(sti[FYLKE_ELLER_SAMEVALGDISTRIKT - 1]);
            } else {
                område = ((Land) område).getFylke(sti[FYLKE_ELLER_SAMEVALGDISTRIKT - 1]);
            }
        }
        if (sti.length >= KOMMUNE) {
            område = ((Fylke) område).getKommune(sti[KOMMUNE - 1]);
        }
        if (sti.length == BYDEL_ELLER_STEMMEKRETS) {
            if (sti[BYDEL_ELLER_STEMMEKRETS - 1].length() == KRETSNUMMER_SIFRE) { // kretsnummer er 4-sifret, bydelsnumre er 1/2-sifret
                område = ((Kommune) område).getStemmekrets(sti[BYDEL_ELLER_STEMMEKRETS - 1]);
            } else {
                område = ((Kommune) område).getBydel(sti[BYDEL_ELLER_STEMMEKRETS - 1]);
            }
        }
        return område;
    }

    private static void leggTilMerknader(Område område, int valgÅr, JsonObjectWrapper rapp) {
        JsonArray merknader = rapp.leggTilTabell(MERKNADER);
        Merknader.leggTilMerknader(område, valgÅr, merknader);
    }

    private static void leggTilMandaterAggregert(JsonObjectWrapper rapp, Område område, PartiMandater totalPM) {
        JsonObjectWrapper mandater = rapp.leggTilObjekt(MANDATER);
        int antall = område.getAntallMandater();
        mandater.leggTilTall(ANTALL, antall);
        if (isStemmekrets(område)) {
            mandater.leggTilTall(ENDRING, (Number)null);
        } else {
            mandater.leggTilTall(ENDRING, antall - totalPM.getResultatData().getNestsisteAntall());
        }
    }

    private static void leggTilStemmer(Område område, JsonObjectWrapper rapp, PartiStemmer totalPS) {
        JsonObjectWrapper stemmer = rapp.leggTilObjekt(STEMMER);
        stemmer.leggTilTall(TOTAL, totalPS.getStemmer());
        stemmer.leggTilTall(FHS, totalPS.getFhs());
        stemmer.leggTilTall(TOTAL_FORKASTEDE, område.getAntallStsForkastede());
        stemmer.leggTilTall(FHS_FORKASTEDE, område.getAntallStsFhsForkastede());
        JsonObjectWrapper stemmegiving = rapp.leggTilObjekt(STEMMEGIVING);
        stemmegiving.leggTilTall(TOTAL_GODKJENTE, område.getAntallStgGodkjente());
        stemmegiving.leggTilTall(FHS_GODKJENTE, område.getAntallStgFhsGodkjente());
        stemmegiving.leggTilTall(TOTAL_FORKASTEDE, område.getAntallStgForkastede());
        stemmegiving.leggTilTall(FHS_FORKASTEDE, område.getAntallStgFhsForkastede());
    }

    private static void leggTilFrammøte(Område område, JsonObjectWrapper rapp) {
        JsonObjectWrapper frammøte = rapp.leggTilObjekt(FRAMMOTE);
        Float frammøteProsent = område.beregnFrammoteProsentIaar();
        frammøte.leggTilTall(PROSENT, Format.pstNN(frammøteProsent));
        if (isStemmekrets(område)) {
            frammøte.leggTilTall(ENDRING, (Number) null);
        } else {
            if (frammøteProsent == null) {
                frammøte.leggTilTall(ENDRING, (Number) null);
            } else {
                frammøte.leggTilTall(ENDRING, Format.pstEndringNN(område.beregnFrammoteProsentIaar(), område.beregnFrammoteProsentNestsiste()));
            }
        }
        Float frammøteProsentStemmegiving = område.beregnFrammøteProsentStemmegiving();
        frammøte.leggTilTall(PROSENT_STEMMEGIVING, Format.pstNN(frammøteProsentStemmegiving));
    }

    private static void leggTilOpptalt(Område område, JsonObjectWrapper rapp) {
        JsonObjectWrapper opptalt = rapp.leggTilObjekt(OPPTALT);
        opptalt.leggTilTall(FORELOPIG_FHS, Format.pst(område.beregnOpptaltProsent(ForeløpigEndeligOppgjør.FORELØPIG, ValgtingForhand.F)));
        opptalt.leggTilTall(FORELOPIG_VTS, Format.pst(område.beregnOpptaltProsent(ForeløpigEndeligOppgjør.FORELØPIG, ValgtingForhand.V)));
        opptalt.leggTilTall(FORELOPIG, Format.pst(område.beregnOpptaltProsent(ForeløpigEndeligOppgjør.FORELØPIG, null)));
        opptalt.leggTilTall(ENDELIG, Format.pst(område.beregnOpptaltProsent(ForeløpigEndeligOppgjør.ENDELIG, null)));
        opptalt.leggTilTall(OPPGJOR, Format.pst(område.beregnOpptaltProsent(ForeløpigEndeligOppgjør.OPPGJØR, null)));
    }

    private static void leggTilPrognoseFlagg(JsonObjectWrapper rapp, PartiMandater totalPM, ValgType vt, Område område) {
        JsonObjectWrapper partiandelPrognose = rapp.leggTilObjekt(PROGNOSE);
        if (totalPM.getPrognoseData().erPrognose()) {
            partiandelPrognose.leggTilBool(BEREGNET, true);
            float opptaltProsent = område.beregnOpptaltProsent() * HUNDRE;
            // representerer det største historiske totalavviket etter en analyse av valgene 2009-2015.
            // Se analyse-dok i Confluence: https://confluence.valg.no/display/pm/Dokumenter
            double historiskTotalavvik;
            if (område.getClass() == Land.class && (vt == ValgType.FY || vt == ValgType.ST)) {
                historiskTotalavvik = (HUNDRE - opptaltProsent) / FEMTEN;
            } else {
                historiskTotalavvik = (HUNDRE - opptaltProsent) / SJUOGENHALV;
            }
            partiandelPrognose.leggTilTall(HISTORISK_TOTALAVVIK, Format.enDesimal(historiskTotalavvik));
        } else {
            partiandelPrognose.leggTilBool(BEREGNET, false);
            partiandelPrognose.leggTilTall(HISTORISK_TOTALAVVIK, (Number) null);
        }
    }

    private static void leggTilLenker(Område område, JsonObjectWrapper rapp, int valgår, Map<String, MainRapport.RappCacheEntry> rapporterNavnMap) {
        JsonObjectWrapper lenker = rapp.leggTilObjekt(LINKS);
        JsonArray underOrdnedeOmraader = lenker.leggTilTabell(RELATED);
        for (Område underordnetOmråde : område.getUnderordnedeRapportOmråder()) {
            JsonObjectWrapper underordnet = new JsonObjectWrapper();
            underOrdnedeOmraader.add(underordnet.getJsonObject());
            underordnet.leggTilString(NR, underordnetOmråde.getNr());
            underordnet.leggTilString(HREF, "/" + valgår + "/" + underordnetOmråde.getRapportid());
            underordnet.leggTilString(HREF_NAVN, "/" + valgår + "/" + underordnetOmråde.getRapportnavnURLEncLC());
            underordnet.leggTilString(NAVN, underordnetOmråde.getNavn());
            underordnet.leggTilTall(FORELOPIG_FHS, Format.pst(underordnetOmråde.beregnOpptaltProsent(ForeløpigEndeligOppgjør.FORELØPIG, ValgtingForhand.F)));
            underordnet.leggTilTall(FORELOPIG_VTS, Format.pst(underordnetOmråde.beregnOpptaltProsent(ForeløpigEndeligOppgjør.FORELØPIG, ValgtingForhand.V)));
            underordnet.leggTilTall(FORELOPIG, Format.pst(underordnetOmråde.beregnOpptaltProsent(ForeløpigEndeligOppgjør.FORELØPIG, null)));
            underordnet.leggTilTall(ENDELIG, Format.pst(underordnetOmråde.beregnOpptaltProsent(ForeløpigEndeligOppgjør.ENDELIG, null)));
            underordnet.leggTilTall(OPPGJOR, Format.pst(underordnetOmråde.beregnOpptaltProsent(ForeløpigEndeligOppgjør.OPPGJØR, null)));
            underordnet.leggTilString(RAPPORT_GENERERT, finnRapportGenerertTidspunkt(rapporterNavnMap, underordnetOmråde.getRapportid()));
            underordnet.leggTilBool(HAR_UNDERORDNET, underordnetOmråde.getUnderordnedeRapportOmråder().length > 0);
        }

        JsonObjectWrapper self = lenker.leggTilObjekt(SELF);
        self.leggTilString(NR, område.getNr());
        self.leggTilString(HREF, "/" + valgår + "/" + område.getRapportid());
        self.leggTilString(HREF_NAVN, "/" + valgår + "/" + område.getRapportnavnURLEncLC());
        self.leggTilString(NAVN, område.getNavn());
        self.leggTilTall(FORELOPIG_FHS, Format.pst(område.beregnOpptaltProsent(ForeløpigEndeligOppgjør.FORELØPIG, ValgtingForhand.F)));
        self.leggTilTall(FORELOPIG_VTS, Format.pst(område.beregnOpptaltProsent(ForeløpigEndeligOppgjør.FORELØPIG, ValgtingForhand.V)));
        self.leggTilTall(FORELOPIG, Format.pst(område.beregnOpptaltProsent(ForeløpigEndeligOppgjør.FORELØPIG, null)));
        self.leggTilTall(ENDELIG, Format.pst(område.beregnOpptaltProsent(ForeløpigEndeligOppgjør.ENDELIG, null)));
        self.leggTilTall(OPPGJOR, Format.pst(område.beregnOpptaltProsent(ForeløpigEndeligOppgjør.OPPGJØR, null)));

        Område overordnetOmråde = område.getOverordnetOmråde();
        if (overordnetOmråde == null) {
            JsonObjectWrapper up = lenker.leggTilObjekt("up");
            up.leggTilString(NR, null);
            up.leggTilString(HREF, "/" + valgår);
            up.leggTilString(HREF_NAVN, "/" + valgår);
            up.leggTilString(NAVN, null);
        } else {
            JsonObjectWrapper up = lenker.leggTilObjekt("up");
            up.leggTilString(NR, overordnetOmråde.getNr());
            up.leggTilString(HREF, "/" + valgår + "/" + overordnetOmråde.getRapportid());
            up.leggTilString(HREF_NAVN, "/" + valgår + "/" + overordnetOmråde.getRapportnavnURLEncLC());
            up.leggTilString(NAVN, overordnetOmråde.getNavn());
        }
    }

    private static String finnRapportGenerertTidspunkt(Map<String, MainRapport.RappCacheEntry> rapporterNavnMap, String rapportNavn) {
        MainRapport.RappCacheEntry entry = rapporterNavnMap.get(rapportNavn);
        if (entry != null) {
            String rapportStr = entry.getJson();
            int rgPos = rapportStr.indexOf(RAPPORT_GENERERT);
            return rapportStr.substring(rgPos + TIDSPUNKT_OFFSET_START, rgPos + TIDSPUNKT_OFFSET_START + TIDSPUNKT_STR_LENGDE);
        } else {
            return FellesRapportelementer.DUMMY_TMS; // Brukes bare i tester - dette er et dummy-tms
        }
    }

    private static boolean isStemmekrets(Område område) {
        return område.getClass() == Stemmekrets.class;
    }
}
