package com.github.oxal;

import org.graphstream.algorithm.Toolkit;
import org.graphstream.algorithm.generator.BarabasiAlbertGenerator;
import org.graphstream.algorithm.generator.Generator;
import org.graphstream.algorithm.generator.RandomGenerator;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import org.graphstream.graph.implementations.DefaultGraph;
import org.graphstream.graph.implementations.SingleGraph;
import org.graphstream.stream.file.FileSource;
import org.graphstream.stream.file.FileSourceEdge;

import java.io.*;
import java.util.ArrayList;
import java.util.Random;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;

public class Epidemi {

    private Graph g;
    private Node origin;
    private Consumer<Node> infection;
    private Consumer<Node> soin;
    private Consumer<Graph> before;
    private Predicate<Node> isInfected;
    public static final String INFECTED = "infected";
    public static final String IMMUNE = "immune";
    public static final Random random = new Random();

    public ArrayList<Integer> countInfected;
    private int time;

    public Epidemi(Graph g) {
        this.g = g;
        countInfected = new ArrayList<>();
        before = a -> {};
    }

    public void init(Node n){
        origin = n;
    }

    public Consumer<Node> getInfection() {
        return infection;
    }

    public void setInfection(Consumer<Node> infection) {
        this.infection = infection;
    }

    public Consumer<Node> getSoin() {
        return soin;
    }

    public void setSoin(Consumer<Node> soin) {
        this.soin = soin;
    }

    public Predicate<Node> getIsInfected() {
        return isInfected;
    }

    public Consumer<Graph> getBefore() {
        return before;
    }

    public void setBefore(Consumer<Graph> before) {
        this.before = before;
    }

    public void setIsInfected(Predicate<Node> isInfected) {
        this.isInfected = isInfected;
    }

    private Stream<Node> getInfectedNodeStream(){
        return g.getNodeSet().stream().filter(isInfected);
    }

    public ArrayList<Integer> getCountInfected() {
        return countInfected;
    }

    public int getCountInfected(int i) {
        return countInfected.get(i);
    }

    public int getTime(){
        return time;
    }

    public Graph getGraph(){
        return g;
    }

    public void run(int t, double ratioEpi, double ratioSoin){
        time = t;
        countInfected = new ArrayList<>();
        g.getNodeSet().forEach(n -> soin.accept(n));
        infection.accept(origin);

        before.accept(g);

        countInfected.add((int) getInfectedNodeStream().count());
        for (int i = 0; i < t; i++){
            getInfectedNodeStream().forEach(n -> {
                n.getEdgeSet()
                        .forEach(e -> {
                            double d = random.nextDouble();
                            if (ratioEpi > d && isInfected.test(n)){
                                getInfection().accept(e.getOpposite(n));
                            }
                        });

            });


            g.getNodeSet().forEach(n -> {
                double d = random.nextDouble();
                if (ratioSoin > d){
                    getSoin().accept(n);
                }
            });

            countInfected.add((int) getInfectedNodeStream().count());
        }
    }

    public static void stat(Epidemi epidemi, String name){
        // propagation sans condition

        File f = new File("out/" + name);
        try {
            BufferedWriter writer = new BufferedWriter(new FileWriter(f));
            for (int i = 0; i <= epidemi.getTime(); i++){
                writer.append(String.format("%2d%15d%15.5f%n", i, epidemi.getCountInfected(i), ((double)epidemi.getCountInfected(i))/((double)epidemi.getGraph().getNodeCount())).replace(",", "."));
                writer.flush();
            }
            writer.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        String filePath = "src/main/resources/graph.txt";
        Graph g = new DefaultGraph("fichier");
        FileSource fs = new FileSourceEdge();

        fs.addSink(g);

        try {
            fs.readAll(filePath);
        } catch (IOException e) {
            e.printStackTrace();
        }

//        Graph randomGenerator = new SingleGraph("RandomGenerator");
//        Generator gen = new RandomGenerator(Toolkit.averageDegree(g));
//        gen.addSink(randomGenerator);
//        gen.begin();
//        for(int i=0; i<g.getNodeCount(); i++)
//            gen.nextEvents();
//        gen.end();



        Epidemi epidemi = new Epidemi(g);
        epidemi.init(Toolkit.randomNode(g));
        epidemi.setInfection(n -> {
            n.setAttribute(INFECTED, true);
        });
        epidemi.setSoin(n -> {
            n.setAttribute(INFECTED, false);
        });
        epidemi.setIsInfected(n -> n.hasAttribute(INFECTED) && n.<Boolean>getAttribute(INFECTED));
//        epidemi.run(90, 1.0/7.0, 1.0/14.0);
//        stat(epidemi, "epi_random_sans_condi.gnu");

        epidemi.setInfection(n -> {
            if (!n.hasAttribute(IMMUNE) || !n.<Boolean>getAttribute(IMMUNE)){
                n.setAttribute(INFECTED, true);
            }
        });
        epidemi.setSoin(n -> {
            n.setAttribute(INFECTED, false);
        });
        epidemi.setBefore(graph -> {
            Toolkit.randomNodeSet(graph, graph.getNodeCount()/2)
                    .stream()
                    .filter(epidemi.getIsInfected().negate())
                    .forEach(n -> {
                        n.setAttribute(IMMUNE, true);
                    });
        });
//        epidemi.run(90, 1.0/7.0, 1.0/14.0);
//        stat(epidemi, "epi_random_immu_alea.gnu");

        epidemi.setBefore(graph -> {
            Toolkit.randomNodeSet(graph, graph.getNodeCount()/2)
                    .stream()
                    .filter(epidemi.getIsInfected().negate())
                    .forEach(n -> {
                        n.setAttribute("g1", true);
                        n.getEdgeSet()
                                .stream().limit(1)
                                .forEach(e -> {
                                    e.getOpposite(n).setAttribute(IMMUNE, true);
                                    e.getOpposite(n).setAttribute("g2", true);
                                });
                    });

            double g1 = graph.getNodeSet()
                    .stream()
                    .filter(n -> n.hasAttribute("g1"))
                    .mapToDouble(n -> (double) n.getDegree())
                    .average().getAsDouble();

            double g2carre = graph.getNodeSet()
                    .stream()
                    .filter(n -> n.hasAttribute("g2"))
                    .mapToDouble(n -> (double) (n.getDegree()*n.getDegree()))
                    .average().getAsDouble();

            double g2 = graph.getNodeSet()
                    .stream()
                    .filter(n -> n.hasAttribute("g2"))
                    .mapToDouble(n -> (double) n.getDegree())
                    .average().getAsDouble();

            System.out.println(g1);
            System.out.println(g2);
            System.out.println(g2carre);
        });

        epidemi.run(90, 1.0/7.0, 1.0/14.0);
//        stat(epidemi, "epi_random_immu_selective.gnu");
    }
}
