package chatapp.Protocol;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;

import chatapp.Controller.ChatApp;
import chatapp.Model.DataBase;
import chatapp.Model.MessageHorodate;
import chatapp.Model.Utilisateur;
import chatapp.View.FenetreSession;
import javafx.application.Platform;

/**
 * <p>
 * Classe représentant une session de clavardage.
 * <br> La classe hérite de la classe Thread et dispose de 2 constructeurs selon que l'on initie une demande de session ou que l'on recoit une demande.
 * <br> Elle possède en attribut:
 * <br> - link:  la socket associée à la session de clavardage
 * <br> - u2: l'utilisateur avec lequel on communique
 * <br> - out: le flux de donnees sortant
 * <br> - in: le flux de donnees entrant
 * <br> - pcs: Le PropertyChangeSupport permettant d'envoyer les notifications de MessageRecu et de FindeSession
 * <br> - derniersMsg: la liste temporaire des derniers messages recus
 * </p>
 */
public class SessionClavardage extends Thread {
    private Socket link;
    private ChatApp app;
    private Utilisateur u2;
    private ObjectOutputStream out;
    private ObjectInputStream in;
    private PropertyChangeSupport pcs;
    private ArrayList<MessageHorodate> derniersMsg; // on met temporairement les derniers msgs pour éviter les pb de synchro inter-threads
    private int SessionID;

    /**
     * <p> Ce constructeur crée une session de clavardage quand on recoit une requete</p>
     * @param link La socket associee a la requete TCP que l'on a recu
     * @param app L'instance de chatapp
     */
    public SessionClavardage(Socket link, ChatApp app) {
        this.setLink(link);
        this.setApp(app);
        try {
            this.setOut(new ObjectOutputStream(link.getOutputStream()));
            this.setIn(new ObjectInputStream(link.getInputStream()));
        }catch(Exception e) {
            e.getStackTrace();
        }
        this.derniersMsg = new ArrayList<MessageHorodate>();
        this.SessionID = 2;
        this.pcs = new PropertyChangeSupport(this);
        this.start();
    }

    /**
     * <p> Le constructeur associe a l'envoi d'une requete TCP, lorsque que l'on veut initier une session de clavardage avec un utilisateur connecte</p>
     * @param u2 L'utilisateur avec qui l'on souhaite démarrer une session de clavardage
     * @param app L'instance de chataap associee
     */
    public SessionClavardage(Utilisateur u2, ChatApp app) {
        this.setU2(u2);
        this.setApp(app);
        try {
            Socket s = new Socket(u2.getIp(),5000);
            this.setOut(new ObjectOutputStream(s.getOutputStream()));
            this.setIn(new ObjectInputStream(s.getInputStream()));
            this.setLink(s);
        } catch (IOException e) {

            e.printStackTrace();
        }
        this.derniersMsg = new ArrayList<MessageHorodate>();
        this.pcs = new PropertyChangeSupport(this);
        this.start();
        this.initierSession();
        Platform.runLater( () ->
                new FenetreSession(this)
        );
    }

    /**
     * Permet aux PropertyChangeListener de s'abonner aux notifications des evenements
     * @param pcl Le PropertyListener qui souhaite s'abonner sur ce PropertyChangeSupport
     */
    public void addPropertyChangeListener(PropertyChangeListener pcl){
        this.pcs.addPropertyChangeListener("MessageRecu",pcl);
        this.pcs.addPropertyChangeListener("FinDeLaSession",pcl);
    }

    /**
     * <p> Methode permettant d'arreter cette session de clavardage et de prevenir l'autre utilisateur de la fin de la session</p>
     */
    public void arretSession() {
        MessageHorodate msgh = new MessageHorodate(getU2(),getApp().getMe(),".",2);
        try {
            getOut().writeObject(msgh.toString());
        } catch (IOException e) {
            //e.printStackTrace();
        }
        finally {
            try {
                link.close();
            } catch (IOException e) {

            }
        }
    }

    /**
     * <p> Methode permettant d'initier une session de clavardage et de prevenir l'autre utilisateur du debut de la session</p>
     */
    public void initierSession(){
        String isExterne = "false";
        if(this.app.isExterne()){
            isExterne="true"; // on indiquera dans le message si l'on est externe ou interne
        }
        MessageHorodate msgh = new MessageHorodate(getU2(),getApp().getMe(),isExterne,3);
        try {
            getOut().writeObject(msgh.toString());
        } catch (IOException e) {
            //e.printStackTrace();
        }
    }
    /**
      * Methode transformant prenant un string pour le transmettre sous la forme d'un message horodate a l'utilisateur distant
      * @param msg Le texte du message à envoyer
      */
    public void envoiMsg(String msg) {
        MessageHorodate msgh = new MessageHorodate(getU2(),getApp().getMe(),msg,1);
        try {
            getOut().writeObject(msgh.toString());
            //app.getDb().ajoutHistorique(u2.getId(), app.getMe().getId(),msg);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * <p> Methode permettant de recuperer le dernier message recu qui a ete mis dans la liste des messages recus.
     * <br> Utilisée par l'interface graphique qui a ete avertie grace au PropertyChangeSupport</p>
     * @return Le dernier message horodate recu
     */
    public MessageHorodate getDernierMsg() {
        return this.derniersMsg.remove(0);
    }


    /**
     * <p> Methode lancee par le thread, permet de receptionner les messages horodates recus et d'agir en conséquence selon le type de message recu</p>
     */
    public void run() {
        String plaintext = null;
        MessageHorodate msg = null;
        System.out.println("Session demarre");
        //app.getDb().CreationTableHistorique(app.getMe().getId(), u2.getId());
        while(true) {
            try {
                plaintext = (String) getIn().readObject();
                msg = MessageHorodate.stringToMessageHorodate(plaintext);

            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                pcs.firePropertyChange("FinDeLaSession", false, true);
                break;
            }
            if(msg.getType() == 2) {
                pcs.firePropertyChange("FinDeLaSession", false, true);
                try {
                    link.close();
                } catch (IOException e) {
                    break;
                }
                break;
            }
            else if(msg.getType() == 3) {
                System.out.println("Session Initiee");
                this.u2 = msg.getSource() ;
                Platform.runLater( () ->
                        new FenetreSession(this)
                );
            }
            else{
            derniersMsg.add(msg);
            pcs.firePropertyChange("MessageRecu",false,true);}

        }
    }

    /**
     * Getter : Out
     * @return Le flux d'ecriture des messages sortant
     */
    public ObjectOutputStream getOut() {
        return out;
    }

    /**
     * Setter : Out
     * @param out Le flux d'ecriture des messages sortant
     */
    public void setOut(ObjectOutputStream out) {
        this.out = out;
    }

    /**
     * Getter : In
     * @return Le flux de lecture des messages entrant
     */
    public ObjectInputStream getIn() {
        return in;
    }

    /**
     * Setter : In
     * @param in Le flux de lecture des messages entrant
     */
    public void setIn(ObjectInputStream in) {
        this.in = in;
    }

    /**
     * Setter : Link
     * @param link La Socket asscciee a la connexion TCP et donc a la session
     */
    public void setLink(Socket link) {
        this.link = link;
    }

    /**
     * Getter : chatApp
     * @return L'instance de chatApp
     */
    public ChatApp getApp() {
        return app;
    }

    /**
     * Setter : chatApp
     * @param app L'instance de chatApp
     */
    public void setApp(ChatApp app) {
        this.app = app;
    }

    /**
     * Getter : Utilisateur 2
     * @return L'utilisateur avec lequel on communique
     */
    public Utilisateur getU2() {
        return u2;
    }

    /**
     *  Setter : Utilisateur 2
     *  @param u2 L'utilisateur avec lequel on communique
     */
    public void setU2(Utilisateur u2) {
        this.u2 = u2;
    }

    /**
     * Getter : SessionID
     * @return SessionID
     */
    public int getSessionID() {
        return SessionID;
    }

}
