import com.sun.source.tree.WhileLoopTree;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;

public class ReseauFilesAttentes {
    private int duree;//100000
    private double q;
    private double p;
    private  ArrayList<Requete> coordinateur;
    private  ArrayList<Requete> serveur1;
    private  ArrayList<Requete> serveur2;
    private  ArrayList<Requete> sortie;

    public final double tempsService = 10;
    public final double tempsService1 = 100;
    public final double tempsService2 = 200;

    public ReseauFilesAttentes(int duree,double p){
        this.duree = duree;
        this.q = 1./2;
        this.p = p;
        this.coordinateur = new ArrayList<>();
        this.serveur1 = new ArrayList<>();
        this.serveur2 = new ArrayList<>();
        this.sortie = new ArrayList<>();
    }

    public int getDuree() {
        return duree;
    }

    public double getP() {
        return p;
    }

    public ArrayList<Requete> getCoordinateur(){
        return coordinateur;
    }

    public ArrayList<Requete> getSortie(){
        return sortie;
    }

    public double equation4(double lambda) {
        double r = Math.random();
        return -Math.log(1-r)/lambda;
    }

    public void addRequeteCoordinateur(Requete r){
        this.getCoordinateur().add(r);
    }

    public void lesDonneesCoordinateur(double lambda){
        double x=0;
        int y=0;
        double dateEntreeInitiale = 0;
        double dateEntreeCourante = 0;
        double dateSortie = 0;
        while (x < duree)
        {
            x=x+equation4(lambda);
            if(x>duree) break;
            dateEntreeInitiale = x;
            dateEntreeCourante = x;

            if(y==0){
                dateSortie = dateEntreeInitiale+tempsService;

            }else{
                if(dateSortie<dateEntreeInitiale)
                    dateSortie = dateEntreeInitiale+tempsService;
                else
                    dateSortie = dateSortie+tempsService;
            }
            addRequeteCoordinateur(new Requete(y,dateEntreeInitiale,dateEntreeCourante,dateSortie));
            y++;//y pour compter les requetes
        }
    }

    //trier les requetes du tableau coordinateur avec dateEntreeCourante
    public void trierCoordinateur(){
        double dateEntreeCourante;
        int id1;
        double dateEntreeInitiale1;
        double dateEntreeCourante1;
        double dateSortie1;
        for (int j = 0; j < this.getCoordinateur().size(); j++)
            for (int i = 0; i < this.getCoordinateur().size()-1; i++) {
                dateEntreeCourante = this.getCoordinateur().get(i).getDateEntreeCourante();
                id1 = this.getCoordinateur().get(i+1).getId();
                dateEntreeInitiale1  = this.getCoordinateur().get(i+1).getDateEntreeInitiale();
                dateEntreeCourante1 = this.getCoordinateur().get(i+1).getDateEntreeCourante();
                dateSortie1 = this.getCoordinateur().get(i+1).getDateSortie();
                // Si this.getSortie()[j] < this.getSortie()[i]
                // Ensuite, échangez les positions les uns avec les autres.
                if (dateEntreeCourante1 < dateEntreeCourante) {
                    this.getCoordinateur().remove(i+1);
                    this.getCoordinateur().add(i,new Requete(id1,dateEntreeInitiale1,dateEntreeCourante1,dateSortie1));
                }
            }
    }

    //Trier le tableau avec id de la requete,cette méthode est utilisé dans la méthode leTempsDeTraitement
    public void trierSortie(){
        int id;
        int id1;
        double dateEntreeInitiale1;
        double dateEntreeCourante1;
        double dateSortie1;
        for (int j = 0; j < this.getSortie().size(); j++)
            for (int i = 0; i < this.getSortie().size()-1; i++) {
                id = this.getSortie().get(i).getId();
                id1 = this.getSortie().get(i+1).getId();
                dateEntreeInitiale1  = this.getSortie().get(i+1).getDateEntreeInitiale();
                dateEntreeCourante1 = this.getSortie().get(i+1).getDateEntreeCourante();
                dateSortie1 = this.getSortie().get(i+1).getDateSortie();
                // Si this.getSortie()[j] < this.getSortie()[i]
                // Ensuite, échangez les positions les uns avec les autres.
                if (id1 < id) {
                    this.getSortie().remove(i+1);
                    this.getSortie().add(i,new Requete(id1,dateEntreeInitiale1,dateEntreeCourante1,dateSortie1));
                }
            }
    }

    //Le temps de traitement de chaque requête traitée
    public String leTempsDeTraitement(){
        this.trierSortie();
        String contenu ="";
        for(int i=0;i<this.getSortie().size();i++){
            contenu += this.getSortie().get(i).getId() + " "+ (this.getSortie().get(i).getDateSortie()-this.getSortie().get(i).getDateEntreeInitiale())+"\n";
        }
        return contenu;
    }

    public double leTempsMoyen(){
        double moyen = 0;
        for(int i=0;i<this.getSortie().size();i++){
            moyen += this.getSortie().get(i).getDateSortie() - this.getSortie().get(i).getDateEntreeInitiale();
        }
        return moyen/this.getSortie().size();
    }


    //après appelant la méthode processus,on obtient le tableau sortie qui contient peut-etre des requetes(dateSortie > duree),il faut appeler la methode sortieALaFin() pour supprimer les requetes(dateSortie > duree)
    public void processus(){
        double r;//pour choisir le serveur
        double r1;//pour choisir si la requete sort ou pas
        //stocker les données de la requete precedente dans le serveur1
        int id1 = 0;
        double dateEntreeInitiale1 = 0;
        double dateEntreeCourante1 = 0;
        double dateSortie1 = 0;
        //stocker les données de la requete precedente dans le serveur2
        int id2 = 0;
        double dateEntreeInitiale2 = 0;
        double dateEntreeCourante2 = 0;
        double dateSortie2 = 0;

        int nb = 0;//Le nombre de requêtes entrées dans le système
        int nb1 = 0;
        int nb2 = 0;
        while((this.getCoordinateur().size()!=0)&&(this.getCoordinateur().get(0)).getDateEntreeCourante()<this.getDuree()) {
            nb++;
            r = Math.random();
            //si r<=q,on met la requete dans le serveur1,sinon dans le serveur2
            if(r<=q){
                serveur1.add(this.getCoordinateur().get(0));
                nb1++;
                if(dateEntreeCourante1==0){ //la condition n'a pas de requete précedente
                    id1 = this.getCoordinateur().get(0).getId();
                    dateEntreeInitiale1 = this.getCoordinateur().get(0).getDateEntreeInitiale();
                    dateEntreeCourante1 = serveur1.get(0).getDateSortie();
                    dateSortie1 = dateEntreeCourante1 +tempsService1;
                }else{//la condition a la requete précedente
                    if (serveur1.get(0).getDateSortie() < dateSortie1){
                        id1 = this.getCoordinateur().get(0).getId();
                        dateEntreeInitiale1 = this.getCoordinateur().get(0).getDateEntreeInitiale();
                        dateEntreeCourante1 = dateSortie1;
                        dateSortie1 = dateEntreeCourante1 +tempsService1;
                    }else{
                        id1 = this.getCoordinateur().get(0).getId();
                        dateEntreeInitiale1 = this.getCoordinateur().get(0).getDateEntreeInitiale();
                        dateEntreeCourante1 = serveur1.get(0).getDateSortie();
                        dateSortie1 = dateEntreeCourante1 +tempsService1;
                    }
                }
                serveur1.get(0).setDateEntreeCourante(dateEntreeCourante1);
                serveur1.get(0).setDateSortie(dateSortie1);
                this.getCoordinateur().remove(0);

                //la condition de sortir
                r1 = Math.random();

                if(this.getP()==0){
                    //sortie.add(new Requete(id1,dateEntreeInitiale1,dateEntreeCourante1,dateSortie1));
                    sortie.add(new Requete(id1,dateEntreeInitiale1,dateEntreeCourante1,dateSortie1));
                }else{
                    if(r1<=this.getP()){
                        this.getCoordinateur().add(new Requete(id1,dateEntreeInitiale1,dateEntreeCourante1,dateSortie1));
                        this.trierCoordinateur();
                    }else{
                        sortie.add(new Requete(id1,dateEntreeInitiale1,dateEntreeCourante1,dateSortie1));
                    }
                }

                serveur1.remove(0);


            }else{
                serveur2.add(this.getCoordinateur().get(0));
                nb2++;
                if(dateEntreeCourante2==0){ //la condition n'a pas de requete précedente
                    id2 = this.getCoordinateur().get(0).getId();
                    dateEntreeInitiale2 = this.getCoordinateur().get(0).getDateEntreeInitiale();
                    dateEntreeCourante2 = serveur2.get(0).getDateSortie();
                    dateSortie2 = dateEntreeCourante2 +tempsService2;
                }else{//la condition a la requete précedente
                    if (serveur2.get(0).getDateSortie() < dateSortie2){
                        id2 = this.getCoordinateur().get(0).getId();
                        dateEntreeInitiale2 = this.getCoordinateur().get(0).getDateEntreeInitiale();
                        dateEntreeCourante2 = dateSortie2;
                        dateSortie2 = dateEntreeCourante2 +tempsService2;
                    }else{
                        id2 = this.getCoordinateur().get(0).getId();
                        dateEntreeInitiale2 = this.getCoordinateur().get(0).getDateEntreeInitiale();
                        dateEntreeCourante2 = serveur2.get(0).getDateSortie();
                        dateSortie2 = dateEntreeCourante2 +tempsService2;
                    }
                }
                serveur2.get(0).setDateEntreeCourante(dateEntreeCourante2);
                serveur2.get(0).setDateSortie(dateSortie2);
                this.getCoordinateur().remove(0);

                //la condition de sortir
                r1 = Math.random();
                if(this.getP()==0){
                    sortie.add(new Requete(id2,dateEntreeInitiale2,dateEntreeCourante2,dateSortie2));
                }else{
                    if(r1<=this.getP()){
                        this.getCoordinateur().add(new Requete(id2,dateEntreeInitiale2,dateEntreeCourante2,dateSortie2));
                        this.trierCoordinateur();
                    }else{
                        sortie.add(new Requete(id2,dateEntreeInitiale2,dateEntreeCourante2,dateSortie2));
                    }
                }

                serveur2.remove(0);
            }
        }

        System.out.println("Le nombre de requêtes entrées dans le système durant la période de simulation est : "+nb);
        System.out.println("Pour chaque serveur1, le nombre de requêtes traitées est : "+nb1);
        System.out.println("Pour chaque serveur2, le nombre de requêtes traitées est : "+nb2);
    }

    //supprimer les requetes qui ont dateSortie > duree
    public void sortieALaFin(){
        for(int i=0;i<this.getSortie().size();i++){
            if(this.getSortie().get(i).getDateSortie()>this.getDuree())
                this.getSortie().remove(i);
        }
    }

    //le parametre id est pour extraire la requete du tableau sortie,s'il n'existe pas dans le tableau,renvoie null
    public Requete extraireRequete(int id){
        for(int i=0;i<this.getSortie().size();i++) {
            if(this.getSortie().get(i).getId() == id) return this.getSortie().get(i);
        }
        return null;
    }

    //calculer le nombre des requetes dans le systeme à chaque instant
    public String nbRequetesMilliseconde(ArrayList<Requete> r) {
        int nb;
        String contenu = "";
        Requete requete;
        for (int i = 1; i <= this.getDuree(); i++) {
            nb = 0;
            for (int j = 0; j < r.size(); j++) {//ici j(id de la requete)
                requete = this.extraireRequete(j);
                if(requete != null){
                    if (requete.getDateEntreeInitiale() < i&&requete.getDateSortie() > i) nb++;
                }else{
                    if (r.get(j).getDateEntreeInitiale() < i) nb++;
                }
            }
            contenu += i + " " + nb + "\n";
        }
        return contenu;
    }




    public void enregistrerLesDonnees(String filename, String partieNom, String contenu) {
        try {
            String filepath = System.getProperty("user.dir") + File.separator + "Data/" + filename
                    + String.valueOf(partieNom) + ".dat";
            System.out.println("Ecriture du fichier : " + filepath);
            FileWriter fw = new FileWriter(filepath);
            BufferedWriter bw = new BufferedWriter(fw);
            bw.write(contenu);
            bw.close();
        } catch ( IOException e) {
            e.printStackTrace();
        }
    }



    public static void main(String[] args) {
        ReseauFilesAttentes rfa = new ReseauFilesAttentes(100000,1./2);
        rfa.lesDonneesCoordinateur(0.005);
        System.out.println("Le nombre de requêtes présentes dans le système : "+rfa.getCoordinateur().size());
        ArrayList<Requete> c =  new ArrayList<>();//copie tout les donnees du table coordinateur dans le table c
        for(int i=0;i<rfa.getCoordinateur().size();i++){
            c.add(new Requete(rfa.getCoordinateur().get(i).getId(),rfa.getCoordinateur().get(i).getDateEntreeInitiale(),rfa.getCoordinateur().get(i).getDateEntreeCourante(),rfa.getCoordinateur().get(i).getDateSortie()));
        }
        rfa.processus();
        rfa.sortieALaFin();
        System.out.println("Le nombre de requêtes sorties dans le système durant la période de simulation est : "+rfa.getSortie().size());
        rfa. enregistrerLesDonnees("dataMilliseconde",rfa.getP()+"_0.005",rfa.nbRequetesMilliseconde(c));
        rfa.enregistrerLesDonnees("leTempsDeTraitement",rfa.getP()+"_0.005",rfa.leTempsDeTraitement());
        System.out.println("Le temps de la moyenne de traitement des requêtes est: "+rfa.leTempsMoyen());
    }
}


