package fr.iutfbleau.projetIHM2022FI2.MP;
import fr.iutfbleau.projetIHM2022FI2.API.*;
import java.sql.Connection;
import org.mariadb.jdbc.*;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import java.util.*;
/**
 * Usine abstraite gérant l'ensemble des changements.
 * 
 */

public class AbstractChangementFactoryPersistant implements AbstractChangementFactory {

    // l'usine à groupe travaillant en tandem avec cette usine.
    private AbstractGroupeFactory agf;
    private JFrame fenetre;
    // On utilise une table de hachage pour retrouver facilement un changement (à partir de son id).
    // Si il y a beaucoup de changements c'est plus rapide que de parcourir toute une liste.
    private HashMap<Integer,Changement> brain;
		/**
		 * Constructeur de l'usine à changement.
		 * @param agf l'usine à groupe travaillant en tandem avec cette usine.
		 * @param fenetre la fenetre principale
		 */
    public AbstractChangementFactoryPersistant(AbstractGroupeFactory agf, JFrame fenetre){
        Objects.requireNonNull(agf,"On ne peut pas créer une usine à changement dont l'usine à groupe parternaire est null");
        this.agf=agf;
        this.fenetre=fenetre;
        this.brain=new HashMap<Integer,Changement>();   
        this.getChange();     
    }
		/**
		 * Constructeur de l'usine à changement.
		 * @param agf l'usine à groupe travaillant en tandem avec cette usine.
		 * @param fenetre la fenetre principale
		 * @param liste la liste des changements
		 */
    public AbstractChangementFactoryPersistant(AbstractGroupeFactory agf, JFrame fenetre, Set<Changement> liste){
        Objects.requireNonNull(agf,"On ne peut pas créer une usine à changement dont l'usine à groupe parternaire est null");
        this.agf=agf;
        this.fenetre=fenetre;
        this.brain=new HashMap<Integer,Changement>();
        for(Changement ch:liste){
            this.brain.put(ch.getId(), ch);
        }
        this.getChange();
    }
                                        
    
    /**
     * permet de récupérer l'usine abstraite pour les groupes qui fonctionne en tandem avec cette usine abstraite
     * @return cette usine abstraite pour les groupes
     */
    public AbstractGroupeFactory getGroupeFactory(){
        return this.agf;
    }
    
    /**
     * permet de récupérer les changements 
     * @return l'ensemble de tous les changements en attente
     */
    public Set<Changement> getAllChangements(){
        this.refresh();
        // la méthode value() d'un hashmap retourne la collection des valeurs.
        // Il faut transformer la collection en Set.
        // Un constructeur de HashSet permet de faire cette opération.
        Set<Changement> out = new HashSet<Changement>(this.brain.values());
        return out;
    }

    /**
     * permet de mettre en oeuvre un changement connu de l'usine abstraite.
     * @param c le changement à mettre en oeuvre.
     * @throws java.lang.NullPointerException si un argument est null
     * @throws java.lang.IllegalStateException si le changement n'a pas de sens en l'état actuel (e.g. étudiant pas dans le groupe de départ a, groupe b inconnu, groupe a inconnu, etc).
     * @throws java.lang.IllegalArgumentException si inconnu de l'usine abstraite
     */
    public void applyChangement(Changement c){
        Objects.requireNonNull(c,"On ne peut pas appliquer un changement qui est null");
        Etudiant e = c.getEtu();
        Groupe a = c.getA();
        Groupe b = c.getB();

        if (!agf.knows(a)) throw new IllegalStateException("Le groupe de départ du changement est inconnu. Impossible à mettre en oeuvre.");
        
        if (!agf.knows(b)) throw new IllegalStateException("Le groupe d'arrivée du changement est inconnu. Impossible à mettre en oeuvre.");
        // pas encore implanté.
        // if(!agf.getGroupesOfEtudiant(e).contains(a)) throw new IllegalStateException("Le groupe de départ ne contient pas l'étudiant. Impossible à mettre en oeuvre.");
            
        agf.dropFromGroupe(a,e);
        agf.addToGroupe(b,e);
        // En cas de succès, on enlève le changement du cerveau
        this.deleteChangement(c);
        for(Changement ch: this.getAllChangements()){
            if(ch.getB().getSize()+1==ch.getB().getMax()){
                this.deleteChangement(ch);
            }
        }
    }

    
    /**
     * permet de supprimer un changement connu de l'usine abstraite.
     * @param c le changement à supprimer.
     * @throws java.lang.NullPointerException si un argument est null
     * @throws java.lang.IllegalArgumentException si inconnu de l'usine abstraite
     */
    public void deleteChangement(Changement c){
        Objects.requireNonNull(c,"On ne peut pas demander la suppression d'un changement qui est null");
        Connection cnx=this.cnx();
        try{
            PreparedStatement pst=cnx.prepareStatement("DELETE FROM `Changement` where `id`=?");
            pst.setInt(1, c.getId());
            pst.executeUpdate();
            pst.close();
        }catch(SQLException er){
            System.out.println(er.toString());
            if(this.erreurSQL()){
                this.deleteChangement(c);
            }else{
                return;
            }
        }
        this.close(cnx);
        this.brain.remove(c.getId());
    }

    /**
     * permet d'ajouter un nouveau changement.
     *
     * @param A groupe actuel
     * @param B groupe demandé
     * @param e étudiant concerné par le changement
     *
     * @throws java.lang.NullPointerException si un argument est null
     * @throws java.lang.IllegalArgumentException si les groupes ou l'étudiant ne sont pas connus de la factory partenaire, ou e n'appartient pas à A ou A et B ne sont pas frères dans l'arbre des groupes.
     *        
     */
    public void createChangement(Groupe A, Etudiant e, Groupe B){
        Objects.requireNonNull(A,"Le groupe d'origine ne peut pas être null");
        Objects.requireNonNull(B,"Le groupe d'arrivée ne peut pas être null");
        Objects.requireNonNull(e,"L'étudiant ne peut pas être null");
        Changement c = new ChangementNP(A,e,B);
        this.brain.put(Integer.valueOf(c.getId()),c); 
        Connection cnx=this.cnx();
        try{
            PreparedStatement pst=cnx.prepareStatement("SELECT * FROM `Changement` where `idGroupeA`=? AND `idGroupeB`=? AND `idEtudiant`=?; ");
            pst.setInt(1, A.getId());
            pst.setInt(2, B.getId());
            pst.setInt(3, e.getId());
            if(!pst.executeQuery().next()){
                pst.close();
                pst=cnx.prepareStatement("INSERT INTO `Changement` (`idGroupeA`, `idGroupeB`, `idEtudiant`, `id`) VALUES (?, ?, ?, ?);");
                pst.setInt(1, A.getId());
                pst.setInt(2, B.getId());
                pst.setInt(3, e.getId());
                pst.setInt(4, c.getId());
                pst.executeUpdate();
                pst.close();
            }else{
                pst.close();
                JOptionPane.showMessageDialog(this.fenetre, "Vous Avez deja demander a Chnager dans ce Groupe", "erreur", JOptionPane.ERROR_MESSAGE);
            }
        }catch(SQLException er){
            System.out.println(er.toString());
            if(this.erreurSQL()){
                this.createChangement(A, e, B);
            }else{
                return;
            }
        }
        this.close(cnx);
    }

    @Override
    /**
     * permet d'ajouter un nouveau changement. de type 2
     *
     * @param A groupe actuel
     * @param B groupe demandé
     * @param e étudiant concerné par le changement
     * @param raison la raison du changement de type 2
     * @throws java.lang.NullPointerException si un argument est null
     * @throws java.lang.IllegalArgumentException si les groupes ou l'étudiant ne sont pas connus de la factory partenaire, ou e n'appartient pas à A ou A et B ne sont pas frères dans l'arbre des groupes.
     *        
     */
    public void createChangement(Groupe A, Etudiant e, Groupe B, String raison){
        Objects.requireNonNull(A,"Le groupe d'origine ne peut pas être null");
        Objects.requireNonNull(B,"Le groupe d'arrivée ne peut pas être null");
        Objects.requireNonNull(e,"L'étudiant ne peut pas être null");
        Changement c = new ChangementNP(A,e,B, raison);
        this.brain.put(Integer.valueOf(c.getId()),c); 
        Connection cnx=this.cnx();
        try{
            PreparedStatement pst=cnx.prepareStatement("SELECT * FROM `Changement` where `idGroupeA`=? AND `idGroupeB`=? AND `idEtudiant`=?; ");
            pst.setInt(1, A.getId());
            pst.setInt(2, B.getId());
            pst.setInt(3, e.getId());
            if(!pst.executeQuery().next()){
                pst.close();
                pst=cnx.prepareStatement("INSERT INTO `Changement` (`id`, `idGroupeA`, `idGroupeB`, `idEtudiant`, `Raison`) VALUES (?, ?, ?, ?, ?);");
                pst.setInt(1, c.getId());
                pst.setInt(2, A.getId());
                pst.setInt(3, B.getId());
                pst.setInt(4, e.getId());
                pst.setString(5, raison);
                pst.executeUpdate();
                pst.close();
            }else{
                pst.close();
                JOptionPane.showMessageDialog(this.fenetre, "Vous Avez deja demander a Chnager dans ce Groupe", "erreur", JOptionPane.ERROR_MESSAGE);
            }
        }catch(SQLException er){
            System.out.println(er.toString());
            if(this.erreurSQL()){
                this.createChangement(A, e, B, raison);
            }else{
                return;
            }
        }
        this.close(cnx);
    }


     // **********************
    // FONCTION POUR SIMPLIFIER LES Modification BD
    // *********************** 

		/**
		 * permet de se connecter à la base de donnée
		 *
		 * @return la connexion à la base de donnée
		 */
    private Connection cnx(){
        //On se Connecte a la BD
        try{
            Class.forName("org.mariadb.jdbc.Driver");
            Connection cnx = DriverManager.getConnection(
            "jdbc:mariadb://dwarves.iut-fbleau.fr/chaignea",
            "chaignea", "Chaigneauphpmyadmin");
            return cnx;
        }catch(Exception e){
            if(this.erreurCO()==true){
                return this.cnx();
            }
        }
        return null;
    }
    
		/**
		 * permet de détécter une erreur de connexion à la base de donnée
		 * 
		 * @return true si une erreur est détéctée, false sinon
		 *
		 * 
		 */
    private boolean erreurCO(){
        if(JOptionPane.showConfirmDialog(this.fenetre, "erreur connection a la BD reassayer?", "erreur connection", JOptionPane.YES_NO_OPTION)==JOptionPane.YES_OPTION){
            return true;
        }else{
            this.fenetre.dispose();
            return false;
        }
    }
    /**
		 * permet de détécter une erreur SQL
		 * 
		 * @return true si une erreur est détéctée, false sinon
		 *
		 * 
		 */
    private boolean erreurSQL(){
        if(JOptionPane.showConfirmDialog(this.fenetre, "erreur lors de la modification, reasssayer?", "erreur SQL", JOptionPane.YES_NO_OPTION)==JOptionPane.YES_OPTION){
            return true;
        }else{
            return false;
        }
    }

		/**
		 * permet de fermer la connexion à la base de donnée
		 * 
		 * @param clos outil de fermture de la connexion
		 * 
		 */
    private void close(AutoCloseable clos){
        try{
            clos.close();
        }catch(Exception e){
            if(this.erreurCO()==true)
                this.close(clos);
        }
    }

		/**
		 * permet de remplir la liste des changements de l'usine
		 */
    private void getChange(){
        Connection cnx=this.cnx();
        try{
            PreparedStatement pst=cnx.prepareStatement("SELECT * FROM `Changement` NATURAL JOIN Etudiant LEFT OUTER JOIN Groupe ON Groupe.id=(SELECT Groupe.`id-parent` from Groupe where Groupe.id=Changement.idGroupeA); ");
            ResultSet rs=pst.executeQuery();
            while(rs.next()){
                Groupe parent=new GroupeNP(rs.getInt(8), rs.getString(9), rs.getInt(10), rs.getInt(11), TypeGroupe.getType(rs.getString(12)), null);
                Groupe[] ab=new Groupe[2];
                this.agf.refreshALL(parent);
                this.getGroupe(rs.getInt(2), rs.getInt(3), ab, parent);    
                Etudiant e=new EtudiantNP(rs.getString(6), rs.getString(7), rs.getInt(4));
                if(rs.getString(5)!=null){
                    this.brain.put(rs.getInt(1), new ChangementNP(ab[0], e, ab[1], rs.getInt(1), rs.getString(5)));
                }else{
                    this.brain.put(rs.getInt(1), new ChangementNP(ab[0], e, ab[1], rs.getInt(1)));
                }  
            }
            pst.close();
        }catch(SQLException e){
            System.out.println(e.toString());
            if(this.erreurCO())
                this.getChange();
            return;
        }
        this.close(cnx);
    }
		/**
		 * permet de chercher les sous-groupes d'un groupe
		 * 
		 * @param idA id du groupe A
		 * @param idB id du groupe B
		 * @param retour tableau de groupe dans lequel on va stocker les groupes
		 * @param toFind le groupe à comparer (ou groupe père pour la première itération)
		 */
    private void getGroupe(int idA, int idB, Groupe[] retour, Groupe tofind){
        for(Groupe sous:tofind.getSousGroupes()){
            if(idA==sous.getId()){
                retour[0]=sous;
            }
            if(idB==sous.getId()){
                retour[1]=sous;
            }
        }
    }

		/**
		 * pemet de arfricher la liste des changements
		 * 
		 */
    private void refresh(){
        this.brain=new HashMap<Integer,Changement>();
        this.getChange();
    }


}
