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

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;

/**
 * Klassen tilbyr alle konverteringer av stemmekretser og kommuner som må gjøres dersom
 * kretser blir erstattet 1:1 med et annet kretsnummer, eller en kommune blir slått sammen
 * med en annen. Disse konverteringene brukes bare for å bygge opp følgende tabeller
 * <p>
 * historikk_stemmeberettigede_mod
 * historikk_stemmetall_mod
 * historikk_mandater_mod
 * <p>
 * Disse 3 tabellene er laget slik at de inneholder data for de 2 siste valg (sammenlignet med dette valget). De
 * øvrige tabellene er prefixet med et årstall, og skal ikke bli modifisert noen gang (vi endrer ikke historien!)
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Konverteringer {

    private static Endringer kommuneendringer;
    private static Endringer kretsendringer;
    private static Brudd bruddKommune;
    private static Brudd bruddBydel;
    
    private static final String KOMMUNE = "kommune";
    private static final String KRETS = "krets";
    private static final String BRUDD_KOMMUNE = "bruddKommune";
    private static final String BRUDD_BYDEL = "bruddBydel";

    private static final Logger LOG = LogManager.getLogger(Konverteringer.class);



    static {
        try {
            kommuneendringer = lesEndringer(KOMMUNE);
            kretsendringer = lesEndringer(KRETS);
            bruddKommune = lesBruddKommune();
            bruddBydel = lesBruddBydel();
        } catch (IOException e) {
            LOG.error("Feil ved lesing av konverteringsdata", e);
        }
    }

    private static Endringer lesEndringer(String input) throws IOException {
        Endringer endringer = new Endringer();
        lesFeltFraResources(input, (valgÅr, felt) -> endringFelt(endringer, valgÅr, felt));
        return endringer;
    }

    private static void lesFeltFraResources(String input, BiConsumer<Integer, String[]> biConsumer) throws IOException {
        Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath:konverteringer/" + input + "/*.csv");
        for (Resource resource : resources) {
            int valgÅr = finnÅrFraFilnavn(resource.getFilename());
            try (BufferedReader br = new BufferedReader(new InputStreamReader(resource.getInputStream()))) {
                String linje;
                while ((linje = br.readLine()) != null) {
                    String[] felt = linje.split(";");
                    biConsumer.accept(valgÅr, felt);
                }
            }
        }
    }

    private static void endringFelt(Endringer endringer, Integer valgÅr, String[] felt) {
        if (felt.length >= 2 && !felt[0].startsWith("//")) {
            endringer.leggTil(valgÅr, felt[0], felt[1]);
        }
    }

    private static int finnÅrFraFilnavn(String filename) {
        try {
            return Integer.parseInt(filename.substring(0, filename.indexOf('.')));
        } catch (NumberFormatException e) {
            LOG.error("Filnavn må være året endringen gjelder", e);
            throw e;
        }
    }

    private static Brudd lesBruddKommune() throws IOException {
        Brudd brudd = new Brudd();
        lesFeltFraResources(BRUDD_KOMMUNE, (valgÅr, felt) -> bruddKommuneFelt(brudd, valgÅr, felt));
        return brudd;
    }

    private static void bruddKommuneFelt(Brudd brudd, Integer valgÅr, String[] felt) {
        if (felt.length >= 1 && !felt[0].startsWith("//")) {
            brudd.add(valgÅr, felt[0]);
        }
    }

    private static Brudd lesBruddBydel() throws IOException {
        Brudd brudd = new Brudd();
        lesFeltFraResources(BRUDD_BYDEL, (valgÅr, felt) -> bruddBydelFelt(brudd, valgÅr, felt));
        return brudd;
    }

    private static void bruddBydelFelt(Brudd brudd, Integer valgÅr, String[] felt) {
        if (felt.length >= 2 && !felt[0].startsWith("//")) {
            brudd.add(valgÅr, felt[0] + felt[1]);
        }
    }

    public static List<String> kommune(String kommnr, int valgår, KonverteringStrategi konverteringStrategi) {
        List<String> endringer = new ArrayList<>();
        if (konverteringStrategi == KonverteringStrategi.NY_TIL_GAMMEL) {
            endringer = kommuneendringer.finnGamle(valgår, kommnr);
        } else if (konverteringStrategi == KonverteringStrategi.GAMMEL_TIL_NY) {
            endringer = kommuneendringer.finnNye(valgår, kommnr);
        }
        return endringer;
    }

    public static List<String> krets(String kommnr, String kretsnr, int valgår, KonverteringStrategi konverteringStrategi) {
        List<String> endringer = new ArrayList<>();
        if (konverteringStrategi == KonverteringStrategi.NY_TIL_GAMMEL) {
            endringer = kretsendringer.finnGamle(valgår, kommnr + ":" + kretsnr);
        } else if (konverteringStrategi == KonverteringStrategi.GAMMEL_TIL_NY) {
            endringer = kretsendringer.finnNye(valgår, kommnr + ":" + kretsnr);
        }
        return endringer;
    }

    public static boolean ssammenlignbar(String kommNr, int valgår) {
        int sisteValgår = getSisteValgår(valgår);
        return !(bruddKommune.getKommuner(valgår).contains(kommNr) || bruddKommune.getKommuner(sisteValgår).contains(kommNr));
    }

    private static int getSisteValgår(int valgår) {
        return valgår - 2;
    }

    public static boolean nssammenlignbar(String kommNr, int valgår) {
        int sisteValgår = getSisteValgår(valgår);
        int nestSitsteValgår = getNestSitsteValgår(valgår);
        return !(bruddKommune.getKommuner(valgår).contains(kommNr) || bruddKommune.getKommuner(sisteValgår).contains(kommNr) || bruddKommune.getKommuner(nestSitsteValgår).contains(kommNr));
    }

    private static int getNestSitsteValgår(int valgår) {
        return valgår - 4;
    }

    public static boolean ssammenlignbar(String kommNr, int bydelsnr, int valgår) {
        int sisteValgår = getSisteValgår(valgår);
        String kommuneBydel = getKommuneBydel(kommNr, bydelsnr);
        return !(bruddBydel.getKommuner(valgår).contains(kommuneBydel) || bruddBydel.getKommuner(sisteValgår).contains(kommuneBydel));
    }

    public static boolean nssammenlignbar(String kommNr, int bydelsnr, int valgår) {
        int sisteValgår = getSisteValgår(valgår);
        int nestSitsteValgår = getNestSitsteValgår(valgår);
        String kommuneBydel = getKommuneBydel(kommNr, bydelsnr);
        return !(bruddBydel.getKommuner(valgår).contains(kommuneBydel) || bruddBydel.getKommuner(sisteValgår).contains(kommuneBydel) || bruddBydel.getKommuner(nestSitsteValgår).contains(kommuneBydel));
    }

    private static String getKommuneBydel(String kommNr, int bydelsnr) {
        return String.format("%s%02d", kommNr, bydelsnr);
    }
}
