package chatapp.Protocol;

import java.io.IOException;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.Inet4Address;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import chatapp.Controller.*;
import chatapp.Model.*;
/**
 * <p>
 * Classe representant les échanges UDP entre utilisateurs.
 * </p>
 */
public class UDPEchange {

    private static Boolean Connecte = true;
    // True si l'utilisateur peut se connecter avec ce nom d'utilisateur , False sinon

    private static Boolean EcouteEnCours = false;

    /**
     * Getter : Connecte
     * @return Le boolean connecte (True s'il peut se connecter avec ce nom d'utilisateur , False sinon)
     */
    public static Boolean getConnecte() {
        return Connecte;
    }

    /**
     * Setter : Connecte
     * @param value La nouvelle valeur du boolean de connexion
     */
    public static void setConnecte(Boolean value) {
        Connecte = value ;
    }

    private static Boolean PseudoValide = true;
    // True si l'utilisateur a (ou demande) un pseudo valide , False sinon

    /**
     * Getter : Pseudo Valide
     * @return Le boolean PseudoValide (True s'il peut utiliser ce pseudo , False sinon)
     */
    public static Boolean getPseudoValide() {
        return PseudoValide;
    }

    /**
     * Setter : PseudoValise
     * @param value La nouvelle valeur du boolean concernant le pseudo valide
     */
    public static void setPseudoValide(Boolean value) {
        PseudoValide = value ;
    }


    /**<p>
     * Getter : Ip
     * <br>Permet de recuperer l'adresse de l'hote sur le reseau au format IPv4.
     * <br> Si plusieurs adresses sont disponibles (plusieurs cartes réseaux), choisit la premiere trouvée
     * </p>
     * @return L'adresse IP au format IPv4
     */
    public static InetAddress getCurrentIp() {
        try {
            Enumeration<NetworkInterface> networkInterfaces = NetworkInterface
                    .getNetworkInterfaces();
            while (networkInterfaces.hasMoreElements()) {
                NetworkInterface ni = (NetworkInterface) networkInterfaces
                        .nextElement();
                Enumeration<InetAddress> nias = ni.getInetAddresses();
                while(nias.hasMoreElements()) {
                    InetAddress ia= (InetAddress) nias.nextElement();
                    if (!ia.isLinkLocalAddress()
                            && !ia.isLoopbackAddress()
                            && ia instanceof Inet4Address) {
                        return ia;
                    }
                }
            }
        } catch (Exception e) {
            System.out.println("unable to get current IP " + e.getMessage());
        }
        return null;
    }
    /**
     * <p>
     * Méthode permettant d'envoyer un message à tout les utilisateurs à l'aide du protocole UDP
     * </p>
     * @param broadcastMessage Message à transmettre aux utilisateurs
     * @throws IOException
     */
    public static void EnvoiBroadcast(String broadcastMessage) throws IOException {
        int port = 1234 ;
        // Envoie en broadcast à tous les utilsateurs
        for (InetAddress broadcastAddr : listAllBroadcastAddresses()) {
            DatagramSocket socket = new DatagramSocket();
            socket.setBroadcast(true);
            byte[]buffer = broadcastMessage.getBytes();
            DatagramPacket packet = new DatagramPacket( buffer, buffer.length,  broadcastAddr,port);
            socket.send(packet);
            socket.close();
            System.out.println("Broadcast sent with address " + broadcastAddr.toString());
            System.out.println("***********Message envoye***********");
            System.out.println("Dest Ip: " + broadcastAddr.toString());
            System.out.println("Dest port: " + String.valueOf(port));
            System.out.println("Contenu: ");
            System.out.println(broadcastMessage);
            System.out.println("************************************");
        }
    }

    /**
     * <p>
     * Methode permettant la reception de messages d'utilisateurs à l'aide du protocole UDP
     * </p>
     * @param app L'application de chat de l'utilisateur qui receptionne le message
     */
    public static void ecouteUDP(ChatApp app)
    {
        DatagramSocket socket = null;
        ExecutorService exec = Executors.newFixedThreadPool(1000);
        try {
            socket = new DatagramSocket(1234);
        } catch (SocketException e1) {
            e1.printStackTrace();
        }
        byte buffer[] = new byte[1024];
        System.out.println("Ecoute sur le port: 1234");
        while(true)
        {
            DatagramPacket data = new DatagramPacket(buffer,buffer.length);
            try {
                socket.receive(data);
            } catch (IOException e) {
                e.printStackTrace();
            }
            // Un thread est lance à chaque reception d'un message
            System.out.println("Message recu!");
            exec.submit(new RunnerUDP(data,app));
        }
    }

    /**
     * <p>
     * Méthode permettant d'envoyer un message a utilisateur en particulier
     * a l'aide du protocole UDP
     * </p>
     * @param Adress L'adresse IP de l'utilisateur
     * @param Message Message message à transmettre a l'utilisateur
     * @throws IOException
     */
    public static void envoiUnicast( InetAddress Adress , String Message ) throws IOException {
        if (ChatApp.getInstance().isConnecte()){
            DatagramSocket socket = new DatagramSocket();
            byte[]buffer = Message.getBytes();
            DatagramPacket packet = new DatagramPacket( buffer, buffer.length,  Adress, 1234 );
            socket.send(packet);
            socket.close();
            System.out.println("***********Message envoye***********");
            System.out.println("Dest Ip: " + Adress.toString());
            System.out.println("Dest port: " + String.valueOf(1234));
            System.out.println("Contenu: ");
            System.out.println(Message);
            System.out.println("************************************");
        }
    }

    /**
     * <p>
     * Methode permettant de recuperer la liste des adresses de broadcast, chacune associer à une interface de la machine
     * </p>
     */
    static List<InetAddress> listAllBroadcastAddresses() throws SocketException {
        List<InetAddress> broadcastList = new ArrayList<>();
        Enumeration<NetworkInterface> interfaces
                = NetworkInterface.getNetworkInterfaces();
        while (interfaces.hasMoreElements()) {
            NetworkInterface networkInterface = interfaces.nextElement();

            if (networkInterface.isLoopback() || !networkInterface.isUp()) {
                continue;
            }

            networkInterface.getInterfaceAddresses().stream()
                    .map(a -> a.getBroadcast())
                    .filter(Objects::nonNull)
                    .forEach(broadcastList::add);
        }
        return broadcastList;
    }

}



/**
 * <p>
 * Classe implementant l'interface Runnable.
 * <br> Contient les traitements a executer dans un thread lancer par des méthodes de la class UDPEchange
 * </p>
 */
class RunnerUDP implements Runnable {
    final DatagramPacket data ;
    ChatApp app ;

    public RunnerUDP(DatagramPacket data, ChatApp app) {
        this.data= data;
        this.app = app ;
    }

    /**
     * <p>
     * Methode qui redefinie les traitements qui seront executes dans le thread:
     * <br> Met à jour la liste des utilisateurs actifs
     * </p>
     *
     */
    @Override
    public void run() {
        System.out.println("Thread started");
        String received = new String(data.getData(), 0, data.getLength());
        System.out.println("***********Message recu***********");
        System.out.println(received);
        System.out.println("**********************************");
        String Type = received.split("\n")[0];

        //****************************************************************************************************
        //****************************************************************************************************
        //****************************************Demande de connexion****************************************
        //****************************************************************************************************
        //****************************************
        if (Type.equals("Connexion")) { // un utilisateur vient d'arriver sur le reseau
            System.out.println("Reception d'une demande de connexion");
            Utilisateur u =  Utilisateur.stringToUtilisateur(received.split("\n")[1]);
            if (! u.equals(this.app.getMe())) { // On envoit en broadcast mais on ne souhaite pas recevoir de message de nous mÃªme
                String reponse = "Reponse Connexion\n";
                if (!( app.getActifUsers() ).verifierUnicite(u.getPseudo())) {
                    System.out.println("Pseudo deja present dans la liste");
                    reponse += "false\n";
                }
                else {
                    if(!u.getPseudo().equals("root")){
                        System.out.println("Ajout d'un nouvel utilisateur dans la liste des Utilisateurs");
                        ( app.getActifUsers() ).addList(u);
                        reponse += "true\n";
                    }
                }
                reponse += app.getMe().toString();

                try {
                    UDPEchange.envoiUnicast(u.getIp(),reponse);
                }catch(IOException e)
                {
                    System.out.println("Echec de l'envoi du message");
                }

                ( app.getActifUsers() ).afficherListeUtilisateurs();
            }
        }
        //*******************************************************************************************************
        //*******************************************************************************************************
        //****************************************Reponse d'une connexion****************************************
        //*******************************************************************************************************
        //*******************************************************************************************************

        if (Type.equals("Reponse Connexion")) { // Un utilisateur te repond suite Ã  ta demande de connexion
            if((received.split("\n")[1]).equals("true")) {
                Utilisateur u = Utilisateur.stringToUtilisateur(received.split("\n")[2]);
                if(!u.getPseudo().equals("root")) { // on ajoute jamais root c'est le server
                    app.getActifUsers().addList(u);
                    app.getActifUsers().afficherListeUtilisateurs();
                }
                else{
                    int max = received.split("\n").length ;
                    if (max > 3 ){
                        for (int i = 3 ; i < max ; i++){
                            String suite = received.split("\n")[i];
                            // Le server nous envoie les utilisateurs connectés en local ou à distance
                            if (!suite.equals("")){
                                app.getActifUsers().addList(Utilisateur.stringToUtilisateur(suite));
                                System.out.println("root m'a transmis les infos de l'utilisateur " +Utilisateur.stringToUtilisateur(suite).toString() );
                            }
                        }
                    }
                }

            }
            else {
                System.out.println("Pseudo deja pris");
                UDPEchange.setConnecte(false);
            }
        }

        //*******************************************************************************************************************
        //*******************************************************************************************************************
        //****************************************Demande de modification d'un pseudo****************************************
        //*******************************************************************************************************************
        //*******************************************************************************************************************

        if (Type.equals("Demande Modification Pseudo")) {
            Utilisateur Source = Utilisateur.stringToUtilisateur(received.split("\n")[1]);
            if (! Source.equals(this.app.getMe())) { // On envoit en broadcast mais on ne souhaite pas recevoir de message de nous même
                String nouveau = received.split("\n")[2] ;
                String Message = "";
                if(( app.getActifUsers() ).verifierUnicite(nouveau)) {
                    Message = "Bon Choix Pseudo\n" + nouveau ;
                }
                else {
                    Message = "Mauvais Choix Pseudo\n" ;
                }
                System.out.println(Message);
                try {
                    UDPEchange.envoiUnicast(Source.getIp(),Message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        //************************************************************************************************************
        //************************************************************************************************************
        //**************************************** Modification pseudo reussi ****************************************
        //************************************************************************************************************
        //************************************************************************************************************

        if (Type.equals("Modification pseudo reussi")) {
            Utilisateur Source = Utilisateur.stringToUtilisateur(received.split("\n")[1]);
            if (! Source.equals(this.app.getMe())) { // On envoit en broadcast mais on ne souhaite pas recevoir de message de nous même
                String nouveau = received.split("\n")[2] ;
                System.out.println("Je rentre ici");
                if(app.getActifUsers().appartient(Source)) { // On verifie d'abord que Source appartient bien a la liste des utilisateurs actifs
                    System.out.println(Source.getPseudo() + " va etre changer en : " + nouveau);
                    app.getActifUsers().modifierList(Source.getPseudo(), nouveau);
                } else {
                    System.out.println(Source.getPseudo() + " va etre ajouter en : " + nouveau);
                    // Suite a une perte d'un message lors d'une connexion l'utilisateur Source n'apparait pas dans la liste
                    Source.setPseudo(nouveau);
                    app.getActifUsers().addList(Source);
                }
            }
        }
        //*********************************************************************************************************
        //*********************************************************************************************************
        //****************************************Mauvais choix d'un pseudo****************************************
        //*********************************************************************************************************
        //*********************************************************************************************************
        if (Type.equals("Mauvais Choix Pseudo")) {
            System.out.println("Ce choix de pseudo est dÃ©jÃ  pris il te faut en choisir un autre");
            UDPEchange.setPseudoValide(false);
        }

        //******************************************************************************************************************
        //******************************************************************************************************************
        //****************************************Bon choix d'un pseudo*****************************************************
        //******************************************************************************************************************
        //******************************************************************************************************************

        if (Type.equals("Bon Choix Pseudo")) {
            // Il n'y a rien a faire ici
        }

        //*********************************************************************************************************
        //*********************************************************************************************************
        //****************************************Demande d'une deconnexion****************************************
        //*********************************************************************************************************
        //*********************************************************************************************************

        if (Type.equals("Deconnexion")) {
            ( app.getActifUsers() ).supprimerList(Utilisateur.stringToUtilisateur(received.split("\n")[1]));
        }

        //*********************************************************************************************************
        //*********************************************************************************************************
        //*******************************Connexion d'un utilisateur externe****************************************
        //*********************************************************************************************************
        //*********************************************************************************************************
        if (Type.equals("Connexion Externe")) {
            Utilisateur nouveau = Utilisateur.stringToUtilisateur(received.split("\n")[1]);
            app.getActifUsers().addList(nouveau);
        }
    }




}
