package TP3;

import org.graphstream.algorithm.Toolkit;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import org.graphstream.graph.implementations.Graphs;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import static org.graphstream.algorithm.Toolkit.randomNode;

/**
 * La classe Scénario permet de simuler 3 scénarios.
 * Le premier : One ne fait pour stopper l'épidémie
 * Le deuxieme : 50% des individus mettent à jour en parmanence leur anti-virus
 * Le troisieme: 50% des individus qui mettent à jour leur anti-virus + 1 de leur voisin
 */


public class Scenario
{
	private int numero;
	private Graph g;
	private int nbJour;
	private double[] nbInfectes;
	private ArrayList<Node> groupe0;
	private ArrayList<Node> groupe1;
	private double seuil;

	/**
	 * Permet de créer un scénario
	 * @param numero
	 * @param g
	 * @param nbJour
	 */
	public Scenario(int numero, Graph g, int nbJour)
	{
		this.numero     = numero;
		this.g          = g;
		this.nbJour     = nbJour;
		this.nbInfectes = new double[nbJour];
		this.groupe0 = new ArrayList<>();
		this.groupe1 = new ArrayList<>();
		this.seuil   = 0.0;
		this.resetGraphe();
	}

	/**
	 * Permet de choisir le scénario que l'on veut appliquer
	 * @return liste des personnes infectés par jour
	 */
	public void run()
	{
		if(this.numero == 1) scenario1();
		if(this.numero == 2) scenario2();
		if(this.numero == 3) scenario3();
	}

	/**
	 * Effectue le scénario 1 sur un graphe, pendant une durée
	 * @return liste des personnes infectés par jour
	 */
	private void scenario1()
	{
		List<Node> listeInfectes = new ArrayList<>();
		Node patientZero = randomNode(this.g);
		patientZero.setAttribute("infecte", true);
		listeInfectes.add(patientZero);

		List<Node> listePorteurs = new ArrayList<>();
		List<Node> listeInfectesTmp;

		double nombreInfecte[] = new double[this.nbJour];

		for (int i = 0; i < this.nbJour; i++)
		{
			listeInfectesTmp = new ArrayList<>(listeInfectes);

			for(Node infecte : listeInfectesTmp)
			{
				infecte.neighborNodes().filter(v ->  !v.hasAttribute("infecte")).forEach(v -> {
					int infection = (int) (Math.random()*7);
					if(infection == 0)
					{
						listePorteurs.add(v);
						v.setAttribute("infecte", true);
					}
				});
				int miseAJour = (int) (Math.random()*14);
				if (miseAJour == 0)
				{
					infecte.removeAttribute("infecte");
					listeInfectes.remove(infecte);
				}
			}
			listeInfectes.addAll(listePorteurs);
			listePorteurs.clear();
			nombreInfecte[i] = (double)listeInfectes.size()/ this.g.getNodeCount();
		}

		this.nbInfectes = AssemblerDeuxTableaux(this.nbInfectes, nombreInfecte);
		this.resetGraphe();

	}



	/**
	 * Effectue le scénario 2 sur un graphe, pendant une durée
	 */
	private void scenario2()
	{
		this.resetGraphe();

		List<Node> listeInfectes = new ArrayList<>();

		Node patientZero = randomNode(this.g);
		patientZero.setAttribute("infecte", true);

		listeInfectes.add(patientZero);
		Scenario.setRandomAntiVirus(this.g);
		List<Node> listePorteurs = new ArrayList<>();
		List<Node> listeInfectesTmp;

		double nombreInfecte[] = new double[this.nbJour];


		for (int i = 0; i < this.nbJour; i++)
		{
			listeInfectesTmp = new ArrayList<>(listeInfectes);

			for(Node infecte : listeInfectesTmp)
			{
				infecte.neighborNodes().filter(v -> ! v.hasAttribute("infecte") && ! v.hasAttribute("immuniser")).forEach(v -> {
					int infection = (int) (Math.random()*7);

					if(infection == 0)
					{
						listePorteurs.add(v);
						v.setAttribute("infecte", true);
					}
				});
				int miseAJour = (int) (Math.random()*14);
				if (miseAJour == 0)
				{
					infecte.removeAttribute("infecte");
					listeInfectes.remove(infecte);
				}
			}
			listeInfectes.addAll(listePorteurs);
			listePorteurs.clear();
			nombreInfecte[i] = (double)listeInfectes.size()/ this.g.nodes().filter(v-> !(boolean) v.hasAttribute("immuniser")).count();

		}
		this.nbInfectes = AssemblerDeuxTableaux(this.nbInfectes, nombreInfecte);
		this.seuil = this.calculSeuil();
	}

	/**
	 * Effectue le scénario 3 sur un graphe, pendant une durée
	 */
	private void scenario3()
	{
		this.resetGraphe();
		this.groupe0.clear();
		this.groupe1.clear();
		List<Node> listeInfectes = new ArrayList<>();
		List<Node> listePorteurs = new ArrayList<>();

		double nombreInfecte[] = new double[nbJour];

		Node[] nodeDuGraphe= this.g.nodes().toArray(Node[]::new);

		for (int i = 0; i < nodeDuGraphe.length/2; i++)
		{
			Node v = nodeDuGraphe[i];
			this.groupe0.add(v);
			Node[] voisin =  v.neighborNodes().toArray(Node[]::new);

			// ajoute aléatoirement à un voisin l'anti-virus
			if(voisin.length>0)
			{
				int random = (int)(Math.random()*voisin.length-1);
				Node n = voisin[random];
				n.setAttribute("immuniser",true);
				n.removeAttribute("infecte");
				this.groupe1.add(n);
			}
		}

		Node patientZero = randomNode(this.g);
		patientZero.setAttribute("infecte", true);
		patientZero.removeAttribute("immuniser");

		listeInfectes.add(patientZero);
		List<Node> listeInfectesTmp;

		for (int i = 0; i < this.nbJour; i++)
		{
			listeInfectesTmp = new ArrayList<>(listeInfectes);

			for(Node infecte : listeInfectesTmp)
			{
				infecte.neighborNodes().filter(v -> ! v.hasAttribute("infecte") && ! v.hasAttribute("immuniser")).forEach(v -> {
					int infection = (int) (Math.random()*7);
					if(infection == 0)
					{
						listePorteurs.add(v);
						v.setAttribute("infecte", true);
					}
				});

				int miseAJour = (int) (Math.random()*14);
				if (miseAJour == 0)
				{
					infecte.removeAttribute("infecte");
					listeInfectes.remove(infecte);
				}

			}
			listeInfectes.addAll(listePorteurs);
			listePorteurs.clear();
			nombreInfecte[i] = (double)listeInfectes.size() / this.g.nodes().filter(v-> ! v.hasAttribute("immuniser")).count();
		}

		this.nbInfectes = AssemblerDeuxTableaux(this.nbInfectes, nombreInfecte);
		this.seuil = this.calculSeuil();
	}

	/**
	 * Reset le graphe par défaut
	 */
	private void resetGraphe()
	{
		this.g.nodes().forEach(n->{
			n.removeAttribute("infecte");
			n.removeAttribute("immuniser");
		});
	}

	/**
	 * Calcul le degré moyen du groupe0
	 * @return degré moyen du groupe0
	 */
	public  double getGroupe0()
	{
		double degreMoyen = 0;

		for (Node n : this.groupe0)
			degreMoyen += n.getDegree();

		return degreMoyen/this.groupe0.size();
	}

	/**
	 * Calcul le degré moyen du groupe1
	 * @return degré moyen du groupe1
	 */
	public  double getGroupe1()
	{
		double degreMoyen = 0;

		for (Node n : this.groupe1)
			degreMoyen += n.getDegree();

		return degreMoyen/this.groupe1.size();
	}


	/**
	 * Permet de prendre 50% des individus et de les imuniser fasse au virus
	 * @param g
	 */
	private static void setRandomAntiVirus(Graph g)
	{
		List<Node> al = Toolkit.randomNodeSet(g, (int) (g.getNodeCount()/2));
		al.stream().filter(v ->! v.hasAttribute("infecte")).forEach(v-> {
			v.setAttribute("immuniser", true);
		});

	}

	/**
	 * Calcul le seuil
	 * @return le seuil
	 */
	private  double calculSeuil()
	{
		//suppression des noeuds ayant une immunisation.
		Graph gClone = Graphs.clone(this.g);
		this.g.nodes().filter(n->n.hasAttribute("immuniser")).forEach(v -> {
			gClone.removeNode(v.getId());
		});
		double degreCarreMoyen = 0.0;
		double degreMoyen = Toolkit.averageDegree(gClone);

		for (Node n: gClone.nodes().collect(Collectors.toList()))
			degreCarreMoyen += Math.pow(n.getDegree(), 2);

		degreCarreMoyen/= gClone.getNodeCount();

		return degreMoyen/degreCarreMoyen;
	}

	/**
	 * Méthode qui permet de combiner deux tableaux
	 * @param tab1
	 * @param tab2
	 * @return le tabeau combiné
	 */
	private static double[] AssemblerDeuxTableaux(double[] tab1, double[] tab2)
	{
		double[] tabResultat = new double[tab1.length];
		for (int i = 0; i < tab1.length; i++)
			tabResultat[i] = tab1[i] + tab2[i];

		return tabResultat;
	}

	/**
	 * Retourne la variable seuil
	 * @return seuil
	 */
	public double getSeuil(){return this.seuil;}

	/**
	 * Retourne le nombre d'infectés par jour
	 * @return la variable nbInfectes
	 */
	public double[] getNbInfectes(){return this.nbInfectes;}
}
