package no.valg.valgnatt.database;

import no.valg.valgnatt.common.ValgnattException;
import no.valg.valgnatt.database.dao.BydelDao;
import no.valg.valgnatt.database.dao.KandidatDao;
import no.valg.valgnatt.database.dao.KommuneDao;
import no.valg.valgnatt.database.dao.MandatberegningDao;
import no.valg.valgnatt.database.dao.PartiListeDao;
import no.valg.valgnatt.database.dao.StemmekretsDao;
import no.valg.valgnatt.database.dao.TilstandDao;
import no.valg.valgnatt.database.dao.ValgdistriktDao;
import no.valg.valgnatt.database.domene.ValgType;
import no.valg.valgnatt.database.domene.ValgTypeAar;
import no.valg.valgnatt.database.geografi.Bydel;
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.PartiMandater;
import no.valg.valgnatt.database.geografi.PartiStemmer;
import no.valg.valgnatt.database.hibernate.BydelId;
import no.valg.valgnatt.database.hibernate.Kandidat;
import no.valg.valgnatt.database.hibernate.Kommune;
import no.valg.valgnatt.database.hibernate.Mandatberegning;
import no.valg.valgnatt.database.hibernate.Partiliste;
import no.valg.valgnatt.database.hibernate.PartilisteId;
import no.valg.valgnatt.database.hibernate.Stemmekrets;
import no.valg.valgnatt.database.hibernate.StemmekretsId;
import no.valg.valgnatt.database.hibernate.Tilstand;
import no.valg.valgnatt.database.hibernate.Valgdistrikt;
import no.valg.valgnatt.database.hibernate.ValgdistriktId;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.sql.DataSource;
import javax.transaction.Transactional;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Map;
import java.util.TreeMap;

@Service
@Transactional
public class GrunnlagsdataService {

    private static final Logger LOG = LogManager.getLogger(GrunnlagsdataService.class);
    private static final String VALGTYPE = "valgtype";
    private static final String VALGDISTRIKTNR = "valgdistriktnr";
    private static final String PARTIKODE = "partikode";
    private static final String SANTALL = "santall";
    private static final String NSANTALL = "nsantall";
    private static final String SANTALL_UTJ = "santall_utj";
    private static final String NSANTALL_UTJ = "nsantall_utj";
    private static final String PROGNOSE = "prognose";
    private static final String STEMMER = "stemmer";
    private static final String ANTALL_STDM_I_FY = "antall_STDM_i_FY";
    private static final String ANTALL = "antall";
    private static final String ANTALL_UTJ = "antall_utj";
    private static final String KOMMNR = "kommnr";
    private static final String KRETSNR = "kretsnr";
    private static final String STG_F_GODKJENTE = "stg_f_godkjente";
    private static final String STG_GODKJENTE = "stg_godkjente";
    private static final String STG_F_FORKASTEDE = "stg_f_forkastede";
    private static final String STG_FORKASTEDE = "stg_forkastede";
    private static final String STS_F_FORKASTEDE = "sts_f_forkastede";
    private static final String STS_FORKASTEDE = "sts_forkastede";
    private static final String REF_ID = "ref_id";
    private static final String ENDRET_TID = "endret_tid";
    private static final String FHS = "fhs";
    private static final String VTS = "vts";
    private static final String LISTESTEMMER = "listestemmer";
    private static final String OPPTALTSTATUS = "opptaltstatus";
    private static final String SFHS = "sfhs";
    private static final String SVTS = "svts";
    private static final String NSFHS = "nsfhs";
    private static final String NSVTS = "nsvts";
    private static final String STILLER_IKKE = "StillerIkke";
    private static final String SISTE_KVOTIENT = "siste_kvotient";
    private static final String SISTE_RANG = "siste_rang";
    private static final String NESTE_KVOTIENT = "neste_kvotient";
    private static final String NESTE_RANG = "neste_rang";

    @Resource
    private PartiListeDao partiListeDao;
    @Resource
    private KommuneDao kommuneDao;
    @Resource
    private ValgdistriktDao valgdistriktDao;
    @Resource
    private BydelDao bydelDao;
    @Resource
    private StemmekretsDao stemmekretsDao;
    @Resource
    private TilstandDao tilstandDao;
    @Resource
    private KandidatDao kandidatDao;
    @Resource
    private MandatberegningDao mandatberegningDao;
    @Resource
    private DataSource dataSource;

    public Tilstand getTilstand(String nokkel) {
        return tilstandDao.findByNokkel(nokkel);
    }

    public void lagreTilstand(Tilstand tilstand) {
        tilstandDao.saveOrUpdate(tilstand);
    }

    public void populerPartilister(Map<PartilisteId, Partiliste> partilister) {
        for (Partiliste partiliste : partiListeDao.list()) {
            partilister.put(partiliste.getId(), partiliste);
        }
    }

    public void initialiserGrunnlagsdatamodell(Map<ValgTypeAar, Land> geografier) {

        Map<ValgTypeAar, Map<String, Kommune>> kommuner = finnKommunerPrValg();
        Map<ValgTypeAar, Map<String, Valgdistrikt>> valgdistrikterAlleValg = lesValgdistrikter();
        Map<ValgTypeAar, Map<String, Stemmekrets>> stemmekretserAlleValg = lesStemmekretser();

        for (Map.Entry<ValgTypeAar, Map<String, Stemmekrets>> valgTypeAarEntry : stemmekretserAlleValg.entrySet()) {
            ValgTypeAar vta = valgTypeAarEntry.getKey();
            // Bydeler er foreløpig like i alle valg, men vi kan ikke gjenbruke objektene på tvers av valg - da blir det feil i datamodellen!
            Map<String, Bydel> bydeler = finnBydeler();
            Map<String, Stemmekrets> stemmekretserEttValg = valgTypeAarEntry.getValue();
            Land land = geografier.get(vta);
            if (land == null) {
                land = new Land(vta.getValgType());
                geografier.put(vta, land);
                if ((vta.getValgType() == ValgType.ST || vta.getValgType() == ValgType.FY) && valgdistrikterAlleValg.get(vta) != null) {
                    Valgdistrikt valgdistriktNorge = valgdistrikterAlleValg.get(vta).get(ValgdistriktId.id(vta, Konstanter.VALGDISTRIKT_NORGE));
                    land.setValgdistriktForUtjevningsmandaterOgLandsprognose(valgdistriktNorge);
                }
            }
            for (Stemmekrets stemmekrets : stemmekretserEttValg.values()) {
                String kommnr = stemmekrets.getId().getKommnr();
                Map<String, Kommune> kommunerMap = kommuner.get(vta);
                if (kommunerMap != null && kommunerMap.get(kommnr) != null) {
                    Kommune kommune = kommunerMap.get(kommnr);
                    land.add(stemmekrets, bydeler, kommune.getKommnavn(), valgdistrikterAlleValg.get(vta).get(ValgdistriktId.id(vta, kommune.getValgdistriktnr())), kommune.isSisteSammenlignbar(), kommune.isNestSisteSammenlignbar());
                } else {
                    // MSI nov 2015: Før 2005 så har vi ikke kommunedata bygget opp, og den geografiske strukturen vil da mangle følgende:
                    // kommunenavn
                    // valgdistrikt-informasjon
                    // bydelinformasjon
                    // fylkesnavn vil hentes fra en statisk map i Fylke-objektet
                    // Dette skal ikke ha noen betydning, da bruken av geografien er ganske begrenset (noe historikk-rapporter) i gamle data
                    land.add(stemmekrets, null, null, null, true, true);
                }
            }
        }
    }

    private Map<ValgTypeAar, Map<String, Stemmekrets>> lesStemmekretser() {
        Map<ValgTypeAar, Map<String, Stemmekrets>> stemmekretser = new TreeMap<>();
        for (Stemmekrets o : stemmekretsDao.list()) {
            ValgTypeAar vta = new ValgTypeAar(o.getId().getValgtype(), o.getId().getValgaar());
            stemmekretser.putIfAbsent(vta, new TreeMap<>());
            stemmekretser.get(vta).put(o.getId().id(), o);
        }
        return stemmekretser;
    }

    private Map<ValgTypeAar, Map<String, Valgdistrikt>> lesValgdistrikter() {
        Map<ValgTypeAar, Map<String, Valgdistrikt>> valgdistrikter = new TreeMap<>();
        for (Valgdistrikt o : valgdistriktDao.list()) {
            ValgTypeAar vta = new ValgTypeAar(o.getId().getValgtype(), o.getId().getValgaar());
            valgdistrikter.putIfAbsent(vta, new TreeMap<>());
            valgdistrikter.get(vta).put(o.getId().id(), o);
        }
        return valgdistrikter;
    }

    private Map<ValgTypeAar, Map<String, Kommune>> finnKommunerPrValg() {
        Map<ValgTypeAar, Map<String, Kommune>> kommuner = new TreeMap<>();
        for (Kommune o : kommuneDao.list()) {
            ValgTypeAar vta = new ValgTypeAar(o.getId().getValgtype(), o.getId().getValgaar());
            kommuner.putIfAbsent(vta, new TreeMap<>());
            kommuner.get(vta).put(o.getId().getKommnr(), o);
        }
        return kommuner;
    }

    private Map<String, no.valg.valgnatt.database.geografi.Bydel> finnBydeler() {
        Map<String, no.valg.valgnatt.database.geografi.Bydel> bydeler = new TreeMap<>();
        for (no.valg.valgnatt.database.hibernate.Bydel bydel : bydelDao.list()) {
            BydelId id = bydel.getId();
            bydeler.put("" + id.getBydelnr(), new no.valg.valgnatt.database.geografi.Bydel("" + id.getBydelnr(), bydel.getBydelnavn(), id.getKommnr(), bydel.isSisteSammenlignbar(), bydel.isNestSisteSammenlignbar()));
        }
        return bydeler;
    }

    /**
     * Vi leser inn alle rader fra mandatberegning/kandidater og populerer datamodellen med alle partier/kandidater
     * som stiller i årets valg. Deretter henter vi ut alle partier som har stilt i tidligere valg og populerer mandathistorikken
     * for både de som stiller i år og de som bare stilte i tidligere år.
     */
    public void initialiserMandaterOgKandidater(GrunnlagsdataCache grunnlagsdataCache) {

        // Leser inn mandat-informasjon
        for (Mandatberegning mandatberegning : mandatberegningDao.list()) {
            ValgType valgType = mandatberegning.getId().getValgtype();
            String valgdistriktnr = mandatberegning.getId().getValgdistriktnr();
            String partikode = mandatberegning.getId().getPartikode();
            Valgdistrikt valgdistrikt = grunnlagsdataCache.getValgdistrikt(valgType, valgdistriktnr);
            Partiliste partiliste = grunnlagsdataCache.getPartiliste(valgType, partikode);
            valgdistrikt.addPartiMandater(new PartiMandater(partikode, partiliste.getPartinavn(), "" + partiliste.getPartikategori(), "" + partiliste.getVisningssekvens(), true));
        }

        // Leser inn kandidat-informasjon
        for (Kandidat kandidat : kandidatDao.list()) {
            ValgType valgType = kandidat.getId().getValgtype();
            Valgdistrikt valgdistrikt = grunnlagsdataCache.getValgdistrikt(valgType, kandidat.getId().getValgdistriktnr());
            PartiMandater pm = valgdistrikt.getPartiMandater(kandidat.getId().getPartikode());
            pm.addKandidat(kandidat);
        }

        // Jeg lager ikke en Hibernate-wrapper for å lese ut denne tabellen en eneste gang i hele systemet
        Connection con = null;
        try {
            con = DataSourceUtils.getConnection(dataSource);
            try (PreparedStatement p = con.prepareStatement("SELECT * FROM historikk_mandater_mod"); ResultSet rs = p.executeQuery()) {
                populerHistorikkFraResultSet(grunnlagsdataCache, rs);
            }
        } catch (SQLException sqle) {
            throw new ValgnattException(sqle);
        } finally {
            DataSourceUtils.releaseConnection(con, dataSource);
        }
    }

    private void populerHistorikkFraResultSet(GrunnlagsdataCache grunnlagsdataCache, ResultSet rs) throws SQLException {
        while (rs.next()) {
            Valgdistrikt valgdistrikt = grunnlagsdataCache.getValgdistrikt(ValgType.valueOf(rs.getString(VALGTYPE)), rs.getString(VALGDISTRIKTNR));
            PartiMandater partiMandater = valgdistrikt.getPartiMandater(rs.getString(PARTIKODE));
            if (partiMandater == null) { // partiet stiller ikke i dette valget
                partiMandater = new PartiMandater(rs.getString(PARTIKODE), rs.getString(PARTIKODE), Konstanter.DEFAULT_PARTIKATEGORI, Konstanter.DEFAULT_VISNINGSSEKVENS, false);
                valgdistrikt.addPartiMandater(partiMandater);
            }
            partiMandater.getResultatData().setDistriktsmandater(new Mandater(0, rs.getInt(SANTALL), rs.getInt(NSANTALL)));
            partiMandater.getResultatData().setUtjevningmandater(new Mandater(0, rs.getInt(SANTALL_UTJ), rs.getInt(NSANTALL_UTJ)));
            partiMandater.getPrognoseData().setDistriktsmandater(new Mandater(0, rs.getInt(SANTALL), rs.getInt(NSANTALL)));
            partiMandater.getPrognoseData().setUtjevningmandater(new Mandater(0, rs.getInt(SANTALL_UTJ), rs.getInt(NSANTALL_UTJ)));
        }
    }

    public void lesResultaterFraMandatberegning(GrunnlagsdataCache grunnlagsdataCache) {
        Connection con = null;
        try {
            con = DataSourceUtils.getConnection(dataSource);
            try (PreparedStatement p = con.prepareStatement("SELECT * FROM mandatberegning"); ResultSet rs = p.executeQuery()) {
                populerPartiMandaterFraResultatSet(grunnlagsdataCache, rs);
            }
        } catch (SQLException sqle) {
            throw new ValgnattException(sqle);
        } finally {
            DataSourceUtils.releaseConnection(con, dataSource);
        }
    }

    private void populerPartiMandaterFraResultatSet(GrunnlagsdataCache grunnlagsdataCache, ResultSet rs) throws SQLException {
        while (rs.next()) {
            ValgType valgType = ValgType.valueOf(rs.getString(VALGTYPE));
            Valgdistrikt valgdistrikt = grunnlagsdataCache.getValgdistrikt(valgType, rs.getString(VALGDISTRIKTNR));

            PartiMandater partiMandater = valgdistrikt.getPartiMandater(rs.getString(PARTIKODE));
            boolean prognose = "1".equals(rs.getString(PROGNOSE));
            MandatData mandatData = prognose ? partiMandater.getPrognoseData() : partiMandater.getResultatData();
            mandatData.setPrognose(rs.getString(PROGNOSE));
            mandatData.setStemmer(rs.getInt(STEMMER));
            if (valgType == ValgType.FY) {
                mandatData.setAntallDistriktsmandaterGittSTValg(rs.getInt(ANTALL_STDM_I_FY));
            }
            mandatData.getDistriktsmandater().setAntall(rs.getInt(ANTALL));
            mandatData.getDistriktsmandater().setLavesteKvotientMedMandat(rs.getBigDecimal(SISTE_KVOTIENT));
            mandatData.getDistriktsmandater().setLavesteMandatrangMedMandat(rs.getInt(SISTE_RANG));
            mandatData.getDistriktsmandater().setHøyesteKvotientUtenMandat(rs.getBigDecimal(NESTE_KVOTIENT));
            mandatData.getDistriktsmandater().setHøyesteMandatrangUtenMandat(rs.getInt(NESTE_RANG));
            mandatData.getUtjevningmandater().setAntall(rs.getInt(ANTALL_UTJ));
            mandatData.getUtjevningmandater().setLavesteKvotientMedMandat(rs.getBigDecimal(SISTE_KVOTIENT));
            mandatData.getUtjevningmandater().setLavesteMandatrangMedMandat(rs.getInt(SISTE_RANG));
        }
    }

    public void populerPartierPrStemmekrets(GrunnlagsdataCache grunnlagsdataCache) {
        for (ValgType valgType : grunnlagsdataCache.getValgTyper()) { // For hver valgtype...
            populerPartierPrStemmekretsPrValgType(grunnlagsdataCache, valgType);
        }
    }

    private void populerPartierPrStemmekretsPrValgType(GrunnlagsdataCache grunnlagsdataCache, ValgType valgType) {
        for (Stemmekrets stemmekrets : grunnlagsdataCache.getLand(valgType).getStemmekretser().values()) { // ...og for hver stemmekrets i dette landet
            for (PartiMandater partiMandater : stemmekrets.getKommune().getValgdistrikt().getPartiMandater()) { // ...og for hvert parti i dette valgdistriktet
                // opprett partistemmer
                if (partiMandater.isStillerliste()) { //...men bare hvis partier stiller liste i denne valgkretsen
                    Partiliste partiliste = grunnlagsdataCache.getPartiliste(valgType, partiMandater.getPartikode());
                    stemmekrets.setPartistemmer(new PartiStemmer(partiMandater.getPartikode(), partiMandater.getPartinavn(), "" + partiliste.getPartikategori(), ""
                            + partiliste.getVisningssekvens(), true));
                } else {
                    stemmekrets.setPartistemmer(new PartiStemmer(partiMandater.getPartikode(), partiMandater.getPartinavn(), Konstanter.DEFAULT_PARTIKATEGORI,
                            Konstanter.DEFAULT_VISNINGSSEKVENS, false));
                }
            }
        }
    }

    public void populerOppgjørPrKrets(GrunnlagsdataCache grunnlagsdataCache) {
        Connection con = null;
        try {
            con = DataSourceUtils.getConnection(dataSource);
            try (PreparedStatement ps = con.prepareStatement("SELECT * FROM stemmeoppgjor"); ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    ValgType valgType = ValgType.valueOf(rs.getString(VALGTYPE));
                    String kommnr = rs.getString(KOMMNR);
                    String kretsnr = rs.getString(KRETSNR);
                    Stemmekrets stemmekrets = grunnlagsdataCache.getStemmekrets(valgType, kommnr, kretsnr);
                    stemmekrets.setStgFhsGodkjente(rs.getInt(STG_F_GODKJENTE));
                    stemmekrets.setStgGodkjente(rs.getInt(STG_GODKJENTE));
                    stemmekrets.setStgFhsForkastede(rs.getInt(STG_F_FORKASTEDE));
                    stemmekrets.setStgForkastede(rs.getInt(STG_FORKASTEDE));
                    stemmekrets.setStsFhsForkastede(rs.getInt(STS_F_FORKASTEDE));
                    stemmekrets.setStsForkastede(rs.getInt(STS_FORKASTEDE));
                    // sts_godkjente, sts_f_godkjente og sts_blanke er allerede
                    // lastet inn i datamodellen via vanlige stemmetall
                }
            }
        } catch (SQLException sqle) {
            throw new ValgnattException(sqle);
        } finally {
            DataSourceUtils.releaseConnection(con, dataSource);
        }

    }

    public void populerStemmetallPrParti(GrunnlagsdataCache grunnlagsdataCache) {
        Connection con = null;
        try {
            con = DataSourceUtils.getConnection(dataSource);
            // MSI apr 2016: Vi kan kanskje optimalisere her senere, til å bare lese inn de som er sist endret.
            // En slik optimalisering må håndtere at et nytt stemmesett for en krets *ikke*
            // fullstendig overskriver det gamle settet (mulig årsaker:
            // Admin-overføring-feil?, Admin-brukerfeil?, FY-2011-valg (komm: 1526, kl 23))
            // Enn så lenge er denne måten å gjøre det på super-robust - ingen stemmer kan bli borte her!
            try (PreparedStatement ps = con.prepareStatement("SELECT * FROM stemmetall ORDER BY ref_id DESC"); ResultSet rs = ps.executeQuery()) {
                populerStemmetallFraResultSet(grunnlagsdataCache, rs);
            }
        } catch (SQLException sqle) {
            throw new ValgnattException(sqle);
        } finally {
            DataSourceUtils.releaseConnection(con, dataSource);
        }
    }

    private void populerStemmetallFraResultSet(GrunnlagsdataCache grunnlagsdataCache, ResultSet rs) throws SQLException {
        boolean lagretSisteStemmeskjemaRef = false;
        while (rs.next()) {
            int refId = rs.getInt(REF_ID);
            Timestamp originalTimestamp = rs.getTimestamp(ENDRET_TID);
            if (!lagretSisteStemmeskjemaRef) {
                grunnlagsdataCache.setSisteStemmeskjemaId(new SisteStemmeskjemaId(refId, originalTimestamp));
                lagretSisteStemmeskjemaRef = true;
            }
            ValgType valgType = ValgType.valueOf(rs.getString(VALGTYPE));
            String kommnr = rs.getString(KOMMNR);
            String kretsnr = rs.getString(KRETSNR);
            String partikode = rs.getString(PARTIKODE);
            Stemmekrets stemmekrets = grunnlagsdataCache.getStemmekrets(valgType, kommnr, kretsnr);
            if (refId >= stemmekrets.getRefId()) {
                PartiStemmer partistemmer = stemmekrets.getParti(partikode);
                if (partistemmer != null) {
                    partistemmer.setFhs(rs.getInt(FHS));
                    partistemmer.setVts(rs.getInt(VTS));
                    partistemmer.setListestemmer(rs.getInt(LISTESTEMMER));
                }
                stemmekrets.setRefId(refId);
                stemmekrets.setOpptaltStatus(rs.getInt(OPPTALTSTATUS));
                stemmekrets.setEndretTms(originalTimestamp);
            }
            // Oppdaterer datamodellen med at opptelling har startet. Dette er nødvendig for å få korrekte differ på endringer av antall mandater
            Valgdistrikt valgdistrikt = stemmekrets.getKommune().getValgdistrikt();
            for (PartiMandater pm : valgdistrikt.getPartiMandater()) {
                pm.setHarStartetOpptelling(true);
            }
            if (valgType == ValgType.FY || valgType == ValgType.ST) {
                Valgdistrikt valgdistriktNorge = grunnlagsdataCache.getValgdistrikt(valgType, Konstanter.VALGDISTRIKT_NORGE);
                for (PartiMandater pm : valgdistriktNorge.getPartiMandater()) {
                    pm.setHarStartetOpptelling(true);
                }
            }
        }
    }

    public void populerHistoriskeStemmetallPrParti(GrunnlagsdataCache grunnlagsdataCache) {
        Connection con = null;
        try {
            con = DataSourceUtils.getConnection(dataSource);
            try (PreparedStatement p = con.prepareCall("SELECT * FROM historikk_stemmetall_mod"); ResultSet rs = p.executeQuery()) {
                while (rs.next()) {
                    ValgType valgType = ValgType.valueOf(rs.getString(VALGTYPE));
                    String kommnr = rs.getString(KOMMNR);
                    String kretsnr = rs.getString(KRETSNR);
                    String partikode = rs.getString(PARTIKODE);
                    Stemmekrets stemmekrets = grunnlagsdataCache.getStemmekrets(valgType, kommnr, kretsnr);
                    PartiStemmer partistemmer = oppdaterPartiStemmer(grunnlagsdataCache, valgType, kommnr, partikode, stemmekrets);
                    partistemmer.addSisteFhs(rs.getInt(SFHS));
                    partistemmer.addSisteVts(rs.getInt(SVTS));
                    partistemmer.addNestsisteFhs(rs.getInt(NSFHS));
                    partistemmer.addNestsisteVts(rs.getInt(NSVTS));
                }
            }
        } catch (SQLException sqle) {
            throw new ValgnattException(sqle);
        } finally {
            DataSourceUtils.releaseConnection(con, dataSource);
        }
    }

    private PartiStemmer oppdaterPartiStemmer(GrunnlagsdataCache grunnlagsdataCache, ValgType valgType, String kommnr, String partikode, Stemmekrets stemmekrets) {
        PartiStemmer partistemmer;
        if (stemmekrets == null) { // kretsen kan være null, fordi kretsen ikke finnes lenger (men fantes i tidligere valg) da bruker vi heller stemmekrets
            // 0000
            Stemmekrets nyStemmekrets = grunnlagsdataCache.getStemmekrets(valgType, kommnr, Konstanter.DEFAULT_KRETS);
            partistemmer = nyStemmekrets.getParti(partikode);
            if (partistemmer == null) { // partistemmer kan være null, fordi partiet ikke stiller liste i denne kretsen (eller valget overhodet)
                Partiliste partiliste = grunnlagsdataCache.getPartiliste(valgType, partikode);
                if (partiliste == null) { // partiliste er fortsatt null, fordi partiet ikke stiller liste i dette valget
                    partistemmer = new PartiStemmer(partikode, STILLER_IKKE, Konstanter.DEFAULT_PARTIKATEGORI, Konstanter.DEFAULT_VISNINGSSEKVENS, false);
                } else {
                    partistemmer = new PartiStemmer(partikode, partiliste.getPartinavn(), "" + partiliste.getPartikategori(), "" + partiliste
                            .getVisningssekvens(), false);
                }
                nyStemmekrets.setPartistemmer(partistemmer);
            }
        } else {
            partistemmer = stemmekrets.getParti(partikode);
            if (partistemmer == null) {
                Partiliste partiliste = grunnlagsdataCache.getPartiliste(valgType, partikode);
                String partinavn = STILLER_IKKE;
                if (partiliste != null) {
                    partinavn = partiliste.getPartinavn();
                }
                partistemmer = new PartiStemmer(partikode, partinavn, Konstanter.DEFAULT_PARTIKATEGORI, Konstanter.DEFAULT_VISNINGSSEKVENS, false);
                stemmekrets.setPartistemmer(partistemmer);
            }
        }
        return partistemmer;
    }

    public void populerHistoriskeStemmeberettigede(GrunnlagsdataCache grunnlagsdataCache) {
        Connection con = null;
        try {
            con = DataSourceUtils.getConnection(dataSource);
            try (PreparedStatement p = con.prepareCall("SELECT * FROM historikk_stemmeberettigede_mod"); ResultSet rs = p.executeQuery()) {
                populerHistoriskStemmeberettigedeFraResultatSet(grunnlagsdataCache, rs);
            }
        } catch (SQLException sqle) {
            throw new ValgnattException(sqle);
        } finally {
            DataSourceUtils.releaseConnection(con, dataSource);
        }
    }

    private void populerHistoriskStemmeberettigedeFraResultatSet(GrunnlagsdataCache grunnlagsdataCache, ResultSet rs) throws SQLException {
        while (rs.next()) {
            ValgType valgType = ValgType.valueOf(rs.getString(VALGTYPE));
            String kommnr = rs.getString(KOMMNR);
            String kretsnr = rs.getString(KRETSNR);
            Stemmekrets stemmekrets = grunnlagsdataCache.getStemmekrets(valgType, kommnr, kretsnr);
            if (stemmekrets == null) { // kretsen kan være null, fordi kretsen ikke finnes lenger (men fantes i tidligere valg) da bruker vi heller stemmekrets
                // 0000
                LOG.info("Kretsen " + kretsnr + " i kommune " + kommnr + " finnes ikke i " + valgType + "-" + grunnlagsdataCache.getValgAar() + ", " +
                        "men har stemmer fra tidligere valg. Disse blir lagt til krets " + Konstanter.DEFAULT_KRETS + " og telles fullt ut i " +
                        "diff-til-gamle-valg-beregningene på denne kretsen.");
                stemmekrets = grunnlagsdataCache.getStemmekrets(valgType, kommnr, Konstanter.DEFAULT_KRETS);
            }
            // Vi kan komme til å legge opp stemmeberettigede fra flere kretser på krets 0000, dersom
            // de gamle kretsene har forsvunnet i dette valget. Så lenge vi ikke regner ut fremmøte
            // på krets eller bydelsnivå, så blir dette korrekt.
            stemmekrets.addNestsisteStemmeberettigede(rs.getInt("nssb"));
        }
    }

    public void oppdaterStemmeberettigedeForÅretsValg(GrunnlagsdataCache grunnlagsdataCache) {
        for (Stemmekrets stemmekretsDB : stemmekretsDao.list()) {
            if (stemmekretsDB.getId().getValgaar() == grunnlagsdataCache.getValgAar()) {
                StemmekretsId id = stemmekretsDB.getId();
                Stemmekrets stemmekretsCache = grunnlagsdataCache.getStemmekrets(id.getValgtype(), id.getKommnr(), id.getKretsnr());
                stemmekretsCache.setStemmeberettigede(stemmekretsDB.getStemmeberettigede());
            }
        }
    }

    /**
     * NB! Denne fjerner alle gamle data og tabeller. Denne er kun tenkt brukt i forbindelse med å kjøre kontinuerlige simuleringer
     * med forskjellige varianter av stemmetallsinnsendinger, og skal kunne instrumenteres fra ControlServlet/Simulator
     */
    public void nullstillDatabase() {
        Connection con = null;
        try {
            con = DataSourceUtils.getConnection(dataSource);
            try (PreparedStatement p = con.prepareCall("DROP SCHEMA public CASCADE")) {
                p.executeUpdate();
            }
        } catch (SQLException sqle) {
            throw new ValgnattException(sqle);
        } finally {
            DataSourceUtils.releaseConnection(con, dataSource);
        }
    }

}
