package chatapp.Model;

import java.sql.*;

/**
 * <p>
 * Classe représentant la Base de données.
 * Celle-ci stocke la liste des utilisateurs ainsi que les échanges entre eux
 * </p>
 * Format de la table Utilisateurs
 * <br> +--------+-------------+---------+---------------------+
 * <br> |   ID   |	 Pseudo	   |  Actif	 | Date d'inscription  |
 * <br> +--------+-------------+---------+---------------------+
 * <br> - Id : Correspond à l'ID de l'utilisateur
 * <br> - Pseudo :  Dernier Pseudo utilisé pour cet ID, va etre remplacé par le pseudo actuel avec majPseudo
 * <br> - Date d'inscription : Comme son nom l'indique le jour de l'inscription de cet utilisateur
 * <br> - Actif :  Boolean indiquant si l'utilisateur est actif
 * <br>
 * <br>  Chaque communication entres utilisateurs sera stockée dans une table de la forme
 * <br> +--------------+--------+---------+--------------+
 * <br> | Destinataire | Source | Message | Date d'envoi |
 * <br> +--------------+--------+---------+--------------+
 * <br>  - Destinataire : Id du destinataire du message
 * <br>  - Source : Id de celui qui envoie le message
 * <br>  - Message : Msg envoyé
 * <br>  - Date d'envoi : Date a laquelle Source a envoyé le message à destinataire
 */
public class DataBase {
    private final String DBurl = "jdbc:mysql://srv-bdens.insa-toulouse.fr:3306/tp_servlet_006?" ;
    private Connection connection = null;
    private final String login = "tp_servlet_006";
    private final String pswd = "baePh9ei";

    private static final DataBase instance = null; // Singleton

    /**
     * Constructeur de la database
     * <br> On installe le driver et on établit la connection.
     */
    public DataBase() {
        try {
            //Besoin d'installer le driver JDBC entre java IDE et le system DBMS pour faire un pont entre les deux
            Class.forName("com.mysql.cj.jdbc.Driver");
            System.out.println("Driver Installe");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            System.out.println("Echec installation Driver");
        }
        try {
            //Etablir une connexion , forme : (url, "myLogin", "myPassword");
            // Nabil : DECOMMENTER CETTE LIGNE
            //this.connection = DriverManager.getConnection("jdbc:mysql://localhost/POO_AL_NM?allowPublicKeyRetrieval=true&useSSL=false", "root", "1234");
            // Auriane : DECOMMENTER CETTE LIGNE
            //this.connection = DriverManager.getConnection("jdbc:mysql://localhost:8889/POO_AL_NM?allowPublicKeyRetrieval=true&useSSL=false", "root", "root");
            this.connection = DriverManager.getConnection(this.DBurl,login,pswd);
            System.out.println("Connexion Etablie");
        } catch (SQLException e) {
            e.printStackTrace();
            System.out.println("Echec d'etablissement de la connexion");
        }
    }

    /**
     * Méthode permettant de renvoyer une instance de la classe DataBase
     * @return L'instance du singleton DataBase.
     */
    public static DataBase getInstance() {
        synchronized (DataBase.class) {
            return new DataBase();
        }
    }

    /**
     * Methode permettant de créer une table pour stocker les messages entre deux utilisateurs
     * @param ID1 Id du premier utilisateur
     * @param ID2 Id du second utilisateur
     */
    public void CreationTableHistorique(String ID1 , String ID2) {
        // Pour eviter d'avoir les tables en double
        String petit;
        String grand;
        int comparaison = ID1.compareTo(ID2);
        if (comparaison < 0) {
            petit = ID1;
            grand = ID2 ;
        }
        else {
            petit = ID2;
            grand = ID1 ;
        }
        String nomTable = "Chat_" + petit + "_" + grand ;
        String Requete = "CREATE TABLE IF NOT EXISTS`" +  nomTable +"` (\n" + "`Destinataire` varchar(100) NOT NULL,\n" + "  `Source` varchar(100) NOT NULL,\n" +  "  `Envoi` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n" +  "  `Message` text NOT NULL\n" +  ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ;";
        System.out.println(Requete);
        PreparedStatement Ps = null ;
        //Statement est utilisé pour envoyer une requete SQL à la base de donnee
        try {
            Ps = connection.prepareStatement(Requete);
            System.out.println("Statement cree");
        } catch (SQLException e) {
            e.printStackTrace();
            System.out.println("Echec creation Statement");
        }
        //Execute la donnée SQL statement passe en parametre
        try {
            assert Ps != null;
            Ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
            System.out.println(" Echec executeUpdate ");
        }
    }

    /**
     * Méthode permettant de mettre a jour le pseudo d'un utilisateur en fonction de son ID dans la base de donnee
     * @param ID Id de l'utilisateur qui vient de changer de pseudo
     * @param Pseudo Nouveau Pseudo de l'utilisateur
     */
    public void majPseudo( String ID , String Pseudo) {
        String requete= "UPDATE `Utilisateurs` SET `Pseudo`=? WHERE id=?";
        PreparedStatement Ps = null ;
        //Statement est utilisé pour envoyer une requete SQL à la base de donnee
        try {
            Ps = connection.prepareStatement(requete);
            Ps.setString(1, Pseudo); // ? n°1 devient la valeur contenu dans Pseudo
            Ps.setString(2, ID); // ? n°2 devient la valeur contenu dans ID
            System.out.println("Statement cree");
        } catch (SQLException e) {
            e.printStackTrace();
            System.out.println("Echec creation Statement");
        }
        //Execute la donnée SQL statement passe en parametre
        try {
            assert Ps != null;
            Ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
            System.out.println(" Echec executeUpdate ");
        }
    }



    /**
     * Méthode permettant de mettre a jour le pseudo d'un utilisateur en fonction de son ID dans la base de donnée
     * @param IDdest Id de l'utilisateur Destinataire du message
     * @param IDsrc Id de celui qui envoi le message
     * @param Msg Message envoye entre les deux utilisateurs
     */
    public void ajoutHistorique(String IDdest , String IDsrc, String Msg) {
        String petit;
        String grand;
        int comparaison = IDdest.compareTo(IDsrc);
        if (comparaison < 0) {
            petit = IDdest;
            grand = IDsrc ;
        }
        else {
            petit = IDsrc;
            grand = IDdest ;
        }
        String nomTable = "Chat_" + petit + "_" + grand ;
        String requete= "INSERT INTO `"+ nomTable +"`(`Destinataire`, `Source`, `Message`)  VALUES ( ? , ? , ?)";
        PreparedStatement Ps = null ;
        //Statement est utilisé pour envoyer une requete SQL à la base de donnee
        try {
            Ps = connection.prepareStatement(requete);
            Ps.setString(1, IDdest);  // ? n°1 devient la valeur contenu dans IDdest
            Ps.setString(2, IDsrc);  // ? n°2 devient la valeur contenu dans IDsrc
            Ps.setString(3, Msg);  // ? n°3 devient la valeur contenu dans Msg
            //La date de l'envoi n'a pas besoin d'être renseigné, elle est automatique
            System.out.println("Statement cree");
        } catch (SQLException e) {
            e.printStackTrace();
            System.out.println("Echec creation Statement");
        }
        //Execute la donnée SQL statement passe en parametre
        try {
            assert Ps != null;
            Ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
            System.out.println(" Echec executeUpdate ");
        }
    }


    /**
     * Méthode permettant d'ajouter un utilisateur dans la base de donnée si celui-ci n'existe pas déjà
     * @param ID Id de l'utilisateur que l'on veut rajouter
     * @param Pseudo Pseudo actuelle de l'utilisateur
     */
    public void ajoutUtilisateurs(String ID  , String  Pseudo ) {
        if(this.idExiste(ID)){
            this.majPseudo(ID, Pseudo);
        }
        else {
            // L'utilisateur n'existe pas , on va le rajouter.
            System.out.println("On rajoute l'utilisateur " + Pseudo);
            String requete = "INSERT INTO `Utilisateurs` (`ID`, `Pseudo`, `Actif`)  VALUES ( ? , ? , '1')";
            PreparedStatement Ps = null;
            //Statement est utilisé pour envoyer une requete SQL à la base de donnee
            try {
                Ps = connection.prepareStatement(requete);
                Ps.setString(1, ID); // ? n°1 devient la valeur contenu dans ID
                Ps.setString(2, Pseudo); // ? n°2 devient la valeur contenu dans Pseudo
                System.out.println("Statement cree");
            } catch (SQLException e) {
                e.printStackTrace();
                System.out.println("Echec creation Statement");
            }
            //Execute la donnée SQL statement passe en parametre
            try {
                assert Ps != null;
                Ps.executeUpdate();
            } catch (SQLException e) {
                e.printStackTrace();
                System.out.println(" Echec executeUpdate ");
            }
        }
    }

    /**
     * Méthode permettant de mettre a jour les utilisateurs actuellement actif
     * @param Id Id de l'utilisateur qui vient de se connecter ou déconnecter
     * @param Connecte Boolean true si il est connecté , false sinon
     */
    public void majUtilisateursActifs(Boolean Connecte, String Id) {
        String requete= "UPDATE `Utilisateurs` SET `Actif`=? WHERE id=?";
        PreparedStatement Ps = null ;
        int Actif = Connecte ? 1 : 0; // True -> 1 , False -> 0
        //Statement est utilisé pour envoyer une requete SQL à la base de donnee
        try {
            Ps = connection.prepareStatement(requete);
            Ps.setInt(1, Actif); // ? n°1 devient la valeur contenu dans Actif (1 ou 0)
            Ps.setString(2, Id); // // ? n°2 devient la valeur contenu dans Id
            System.out.println("Statement cree");
        } catch (SQLException e) {
            e.printStackTrace();
            System.out.println("Echec creation Statement");
        }
        //Execute la donnée SQL statement passe en parametre
        try {
            assert Ps != null;
            Ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
            System.out.println(" Echec executeUpdate ");
        }
    }

    /**
     * Méthode permettant de récupérer les N derniers messages envoyés entre 2 utilisateurs
     * @param ID Utilisateur associé a ChatApp
     * @param IDdestinataire Celui avec qui correspond ID
     * @param N Le nombre de messages souhaités
     * @return Les N derniers Messages
     */
    public String recupNMsg(String ID, String IDdestinataire, int N) {
        StringBuilder Msg = new StringBuilder();
        String petit;
        String grand;
        int comparaison = ID.compareTo(IDdestinataire);
        if (comparaison < 0) {
            petit = ID;
            grand = IDdestinataire;
        }
        else {
            petit = IDdestinataire;
            grand = ID ;
        }
        String nomTable = "Chat_" + petit + "_" + grand ;
        String requete = "SELECT * FROM `"+ nomTable +"`";
        Statement stmt = null;
        ResultSet rs = null ;
        try {
            stmt = this.connection.createStatement();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        try {
            assert stmt != null;
            rs = stmt.executeQuery(requete);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        Integer Taille = tailleBDD(nomTable);
        if(Taille < N ){
            N = Taille ;
        }
        for(int i = 0 ; i < N; i++){
            try {
                assert rs != null;
                if( rs.next()){
                    String IDSource = rs.getString("Source");
                    String Envoi = rs.getTimestamp("Envoi").toString();
                    String Message = rs.getString("Message");
                    if (IDSource.equals(ID)) Msg.append("Moi (").append(Envoi).append(") : ").append(Message);
                    else {
                        String PseudoSource = getPseudo(IDSource) ;
                        Msg.append(PseudoSource).append(" (").append(Envoi).append(") : ").append(Message);
                    }
                Msg.append('\n');
                }
                // MSG de la forme :
                // Source (Date) : Texte
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        return Msg.toString();
    }

    /**
     * Méthode permettant de récupérer le pseudo actuel d'un utilisateur en fonction de son ID
     * @param idSource Id de l'utilsateur dont on veut connaitre le pseudo
     * @return Pseudo actuel
     */
    private String getPseudo(String idSource) {
        String Pseudo  = "" ;
        String requete= "SELECT * FROM `Utilisateurs` WHERE `ID` LIKE '" + idSource + "'";
        Statement stmt = null;
        ResultSet rs = null ;
        try {
            stmt = this.connection.createStatement();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        try {
            assert stmt != null;
            rs = stmt.executeQuery(requete);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        try {
            assert rs != null;
            if (rs.next()){
                    Pseudo = rs.getString("Pseudo") ;
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
        }
            return Pseudo ;
    }

    /**
     * Méthode permettant de récupérer les messages d'une plage de donnée [deb,fin] envoyés entre 2 utilisateurs
     * @param ID Utilisateur associé a ChatApp
     * @param IDdestinataire Celui avec qui correspond ID
     * @param deb On veut les messages à partir de l'indice deb
     * @param fin On veut les messages jusqu'a l'indice fin
     * @return Les messages d'une plage de donnée [deb,fin]
     */
    public String recupMsg(String ID, String IDdestinataire, int deb , int fin) {
        StringBuilder Msg = new StringBuilder();
        String petit;
        String grand;
        int comparaison = ID.compareTo(IDdestinataire);
        if (comparaison < 0) {
            petit = ID;
            grand = IDdestinataire;
        }
        else {
            petit = IDdestinataire;
            grand = ID ;
        }
        String nomTable = "Chat_" + petit + "_" + grand ;
        String requete = "SELECT * FROM `"+ nomTable+"`";
        Statement stmt = null;
        ResultSet rs = null ;
        try {
            stmt = this.connection.createStatement();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        try {
            assert stmt != null;
            rs = stmt.executeQuery(requete);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        Integer Taille = tailleBDD(nomTable);
        if(Taille < fin ){
            fin = Taille ;
        }
        for(int i = 0 ; i < fin; i++){
            try {
                assert rs != null;
                if( rs.next()){
                    String IDSource = rs.getString("Source");
                    String Envoi = rs.getTimestamp("Envoi").toString();
                    String Message = rs.getString("Message");
                    if (i >= deb) {
                        if (IDSource.equals(ID)) {
                            Msg.append("Moi (").append(Envoi).append(") : ").append(Message);
                        } else {
                            String PseudoSource = getPseudo(IDSource);
                            Msg.append(PseudoSource).append(" (").append(Envoi).append(") : ").append(Message);
                        }
                        Msg.append('\n');
                    }
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }

        return Msg.toString();
    }

    /**
     * Cette méthode sert à récupérer la taille d'un base de donnée
     * @param nomTable Nom de la table de donnée dont on veut récuperer le nom
     * @return Taille de la BDD
     */
    public Integer tailleBDD(String nomTable) {
        Integer Taille = 0 ;
        String requete = "SELECT * FROM `"+nomTable+ "`";
        Statement stmt = null;
        ResultSet rs = null ;
        try {
            stmt = this.connection.createStatement();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        try {
            assert stmt != null;
            rs = stmt.executeQuery(requete);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        while(true){
            try {
                assert rs != null;
                if (!rs.next()) break;
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            Taille++; // on incremente Taille pour chaque solution trouvée
        }
        return Taille;
    }

    /**
     * Methode permettant de savoir si un utilisateur existe dans la base de donnée 'Utilisateurs'
     * @param ID On recherche l'utilisateur dont l'ID est ID
     * @return True si l'utilisateur existe , False sinon
     */
    public boolean idExiste(String ID){
        boolean existe = false ;
        // Verification que l'utilisateur n'existe pas
        String requete = "SELECT * FROM `Utilisateurs` WHERE `ID` LIKE '" + ID +"'";
        Statement stmt;
        ResultSet rs = null ;
        try {
            stmt = this.connection.createStatement();
            rs = stmt.executeQuery(requete);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        try {
            if(rs.next()){
                existe = (!rs.equals(null));
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return existe;
    }

    /**
     * Méthode permettant de récupérer le nom de la table où sont stockés les messages de 2 utilisateurs.
     * @param U1 Un des 2 utilisateurs
     * @param U2 Le second utilisateur
     * @return Le nom de la table des historiques
     */
    public String getNomTable(Utilisateur U1, Utilisateur U2){
        String petit;
        String grand;
        int comparaison;
        comparaison = U1.getId().compareTo(U2.getId());
        if (comparaison < 0) {
            petit = U1.getId();
            grand = U2.getId();
        }
        else {
            petit = U2.getId() ;
            grand = U1.getId() ;
        }
        return "Chat_" + petit + "_" + grand ;
    }
}
