package chatapp.Controller;

import chatapp.Model.*;
import chatapp.Protocol.*;
import javafx.application.Application;
import javafx.application.Platform;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ChatApp implements PropertyChangeListener {

    /* Liste des utilisateurs actifs */
    private ListUtilisateurs actifUsers ;

    /* ChatApp est associe a un utilisateur */
    private Utilisateur me;

    /* ChatApp est associe a une application qui le lance et que chatapp doit fermer a la fin */
    private Application main;

    private static ChatApp chatapp = null;
    private RunnerEcouteTCP runnerEcouteTCP;
    private RunnerEcouteUDP runnerEcouteUDP;
    private boolean connecte;
    private DataBase db;

    public boolean isConnecte() {
        return connecte;
    }

    /**
     * <p>Constructeur de l'application de chat</p>
     *
     * @param pseudo Pseudo de l'utilisateur
     * @param port Port de communication
     */
    public ChatApp(String pseudo, Integer port, Application main){
        this.actifUsers = new ListUtilisateurs() ;
        // Recuperer adresse IP de l'utilisateur
        InetAddress ip = null ;
        ip = UDPEchange.getCurrentIp();
        this.me = new Utilisateur(pseudo,port,ip);
        this.actifUsers.addList(getMe());

        this.main = main;
        this.db= DataBase.getInstance();
        this.connecte=false;
    }

    /**
     * <p>Methode permettant de créer qu'une seule instance de chatapp</p>
     * @param pseudo le pseudonyme que l'on souhaite donner à l'utilisateur
     * @param port   le port auquel on souhaite receptionner les requêtees UDP
     * @param main   le stage de l'interface graphique qui crée l'instance
     * @return une instance de chatapp
     */
    public static synchronized ChatApp getInstance(String pseudo, Integer port, Application main){
            if (chatapp == null) {
                chatapp = new ChatApp(pseudo, port, main);
            }
        return chatapp;
    }

    /**
     *
     * @return recupere l'instance de chatapp qui a ete creee au prealable
     */
    public static ChatApp getInstance(){
        return chatapp;
    }



	/*public void majHistorique2(MessageHorodate mh, String pseudo) {
		Historique h = getMapHistorique().get(pseudo);
		//h.addMessage(mh);
		getMapHistorique().put(h.getUser2().getPseudo(),h);
	}*/


    /**
     * <p>Modification du pseudo de l'utilisateur
     * Envoie en broadcast ses informations utilisateurs et son nouveau pseudo</p>
     *
     * @param nouveau correspond au nouveau pseudo
     * @return False si modiferPseudo a echoue, True sinon
     */
    public Boolean modifierPseudo(String nouveau) throws IOException {
        // Message que l'on envoie Ã  tous les utilisateurs actifs
        String broadcastMessage = "Demande Modification Pseudo\n" + this.getMe().toString() + "\n" + nouveau + "\n";
        UDPEchange.EnvoiBroadcast(broadcastMessage);
        try {
            Thread.sleep(2000);
            /* L'utilisateur doit attendre la reponse de tous les utilisateurs connectes
             * pour savoir si son pseudo est accepte
             */
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (UDPEchange.getPseudoValide()) {
            System.out.println("Modification pseudo reussie");
            //Envoi un msg en broadcast a tout les utilisateurs pour les prevenir de son nouveau pseudo//
            broadcastMessage = "Modification pseudo reussie\n" + this.getMe().toString() + "\n" + nouveau + "\n";
            UDPEchange.EnvoiBroadcast(broadcastMessage);
            //-------Change son propre nom d'utilisateur-------//
            this.getActifUsers().modifierList(this.getMe().getPseudo(), nouveau);
            this.getMe().setPseudo(nouveau);
            db.majPseudo(this.me.getId(),nouveau);
            System.out.println("Changement pseudo accepte, nouvelle liste des utilisateurs actifs:");
            this.getActifUsers().afficherListeUtilisateurs();
            return true;
        }
        else
        {
            System.out.println("Echec Modification pseudo");
            return false;
        }
    }


    /**
     * <p>Methode appelee lors de la connexion d'un nouvel utilisateur.
     * Il va prevenir les utilisateurs du reseau de son arrivee.</p>
     * @return False si Connexion a echoue, True sinon
     */
    public Boolean connexion() throws IOException {
        // Message que l'on envoie Ã  tous les utilisateurs actifs
        String broadcastMessage = "Connexion\n" + this.getMe().toString() ;
        UDPEchange.EnvoiBroadcast(broadcastMessage);
        try {
            Thread.sleep(2000); // L'utilisateur doit attendre la reponse de tous les utilisateurs connectes
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (UDPEchange.getConnecte()) {
            System.out.println("Connexion reussie");
            return true;
        }
        else
        {
            System.out.println("Connexion echoue");
            UDPEchange.setConnecte(true);
            return false ;
        }
    }

    /**
     * <p>Methode appelee lors de la connexion de l'utilisateur, va prevenir les autres
     * utlisateurs du reseau de l'arrivee de l'utilisateur sur celui-ci</p>
     * @param pseudo le pseudonyme avec lequel l'utilisateur souhaite se connecter
     * @return  False si Connexion a echoue, True sinon
     * @throws IOException
     */
    public Boolean connexion(String pseudo) throws IOException {
        // Message que l'on envoie Ã  tous les utilisateurs actifs
        this.me.setPseudo(pseudo);
        String broadcastMessage = "Connexion\n" + this.getMe().toString() ;
        UDPEchange.EnvoiBroadcast(broadcastMessage);
        try {
            Thread.sleep(2000); // L'utilisateur doit attendre la reponse de tous les utilisateurs connectes
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (UDPEchange.getConnecte()) {
            System.out.println("Connexion reussie");
            this.connecte=true;
            this.db.ajoutUtilisateurs(this.me.getId(),pseudo);
            this.db.majUtilisateursActifs(true,this.me.getId());
            return true;
        }
        else
        {
            System.out.println("Connexion echoue");
            UDPEchange.setConnecte(true);
            return false ;
        }
    }

    /**
     * <p>Lance un thread pour ecouter les requetes TCP
     * Informe le pcs que chatapp ecoute les notifications qu'il envoie</p>
     */
    public void activerEcouteTCP(){
        this.runnerEcouteTCP = new RunnerEcouteTCP(this);
        this.runnerEcouteTCP.addPropertyChangeListener(this);
    }

    /**
     * <p>Demarre une session de clavardage avec l'utilisateur dont le pseudo est entre en parametre</p>
     * @param pseudo le pseudo de l'utilisateur avec lequel on souhaite communiquer
     */
    public void demarrerSession(String pseudo){
        Utilisateur u2 = this.actifUsers.getPseudoList(pseudo);
        SessionClavardage session = new SessionClavardage(u2,this);
        runnerEcouteTCP.addSession(session);
    }

    /**
     * <p>Lance le thread pour receptionner les requetes UDP</p>
     */
    public void activerEcouteUDP(){
        this.runnerEcouteUDP = new RunnerEcouteUDP(this);
        this.runnerEcouteUDP.start();
    }
    /**
     * <p>Methode appelee lors de la deconnexion de l'utilisateur.
     * Il va prevenir les utilisateurs du reseau de son depart.</p>
     *
     */
    public void deconnexion() throws IOException {
        // Message que l'on envoie Ã  tous les utilisateurs actifs
        String broadcastMessage = "Deconnexion\n" + this.getMe().toString() ;
        UDPEchange.EnvoiBroadcast(broadcastMessage);
        db.majUtilisateursActifs(false,this.me.getId());
        this.connecte=false;
        try {
            main.stop();
        } catch (Exception e) {
            e.printStackTrace();
        }
        Platform.exit();
        System.exit(0);
    }

    /**
     * Getter
     * @return Utilisateur associee a ChatApp
     */
    public Utilisateur getMe() {
        return me;
    }

    /**
     * Getter
     * @return Liste des utilisateurs actifs associee a ChatApp
     */
    public ListUtilisateurs getActifUsers() {
        return actifUsers;
    }




    /*public static void main (String[] args) throws IOException {
        ChatApp app = new ChatApp(args[0],Integer.parseInt(args[1]),this.app) ;
        System.out.println("On lance le chatapp");


        ExecutorService execUDP = Executors.newFixedThreadPool(1000);
        execUDP.submit(new RunnerEcouteUDP(app));
        try {
            app.connexion();
        } catch (IOException e) {
            e.printStackTrace();
        }
        ExecutorService execTCP = Executors.newFixedThreadPool(1000);
        System.out.println("On lance l'écoute TCP de chatapp");
        execTCP.submit(new RunnerEcouteTCP(app));
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if(app.getMe().getPseudo().equals("Marvel")) {
            app.getHist("Doudou").afficher10derniers();
            System.out.println("Tentative de connexion avec Doudou");
            TCPEchange.demarrerSession(app, app.actifUsers.getPseudoList("Doudou"));
        }



    }*/

    /**
     * <p>Handler des differents evenements que chatapp ecoute</p>
     * @param evt l'evenement qui a ete indique par le pcs
     */
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        switch (evt.getPropertyName()){
            case "chatCreated" :
                SessionClavardage session = this.runnerEcouteTCP.getSessionClavardage();
                session.addPropertyChangeListener(this);
                break;
        }
    }

    /**
     * <p>Permet de recuperer stage de l'interface graphique qui a cree l'instance</p>
     * @return le stage de l'interface graphique qui a cree l'instance
     */
    public Application getMain() {
        return main;
    }

    /**
     * <p>Permet de recuperer la base de donnee associee</p>
     * @return la base de donnee associee
     */
    public DataBase getDb(){return this.db;}
}
