package controller;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;

import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultCaret;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;

import config.Configuration;
import gui.FenetreChat;
import gui.FenetreMenu;
import model.*;
import network.*;
import server.Request;
import test.App;

public class Agent implements PropertyChangeListener{
	private Contact user;
	private UDPInput BIn;
	private UDPOutput BOut;
	private TCPServer server;
	private InetAddress localAddress;
	private InetAddress broadcast;
	private ArrayList<TCPChat> listTCPOk;
	private ArrayList<TCPChat> listTCPDeg;
	private DataBase db;
		
	private PropertyChangeSupport support;
	
	private boolean open = true;
	
	//Création d'un agent lors de l'inscription (première connexion)
	public Agent(InetAddress address, int portIn, int portOut) throws IOException {
		this.user = new Contact("", address, portIn);
		this.BIn = new UDPInput(user.getAddress(), portIn);
		this.BOut = new UDPOutput(user, portOut);
		this.server = new TCPServer(user.getPort(), user.getAddress());
		this.server.addPropertyChangeListener(this);
		this.localAddress=Tools.getAdress()[0];
		this.broadcast= Tools.getAdress()[1];
		this.db = DataBase.getInstance();
		this.listTCPOk = new ArrayList<TCPChat>();
		this.listTCPDeg = new ArrayList<TCPChat>();
		this.support = new PropertyChangeSupport(this);
		this.BIn.addPropertyChangeListener(this);
		BIn.start();
	}
	
	//Création d'un agent par un user ayant déjà fait une connexion
	public Agent(InetAddress address, int portIn, int portOut, String login) throws IOException {
		this.db = DataBase.getInstance();
		this.user = new Contact(db.getPseudoFromLogin(login), address, portIn);
		this.BIn = new UDPInput(user.getAddress(), portIn);
		this.BOut = new UDPOutput(user, portOut);
		this.server = new TCPServer(user.getPort(), user.getAddress());
		this.server.addPropertyChangeListener(this);
		this.localAddress=Tools.getAdress()[0];
		this.broadcast= Tools.getAdress()[1];
		this.listTCPOk = new ArrayList<TCPChat>();
		this.listTCPDeg = new ArrayList<TCPChat>();
		this.support = new PropertyChangeSupport(this);
		this.BIn.addPropertyChangeListener(this);
		BIn.start();
	}
	
	
	public void propertyChange(PropertyChangeEvent evt) {
    	switch (evt.getPropertyName()) {
    	case "Msg UDP Recu" : 
    		Message msg = BIn.getMessage();
    		try {
				handleMessage(msg);
			} catch (IOException e) {
				e.printStackTrace();
			}
    		break;
    	case "session chat demarree" :
    		TCPChat tcpChat = this.server.getConnexionTCPChat();
    		tcpChat.addPropertyChangeListener(this);
    		this.listTCPOk.add(tcpChat);
    		break;
    	
		case "Msg TCP Recu" : 
			Color c = new Color(25, 94, 125);
			SimpleAttributeSet left = new SimpleAttributeSet();
	        StyleConstants.setAlignment(left, StyleConstants.ALIGN_LEFT);
	        StyleConstants.setForeground(left, c.brighter());
			TCPChat tcpChatCo = this.listTCPOk.get(0);
			MessageChat msgRecu = tcpChatCo.getMessageRecu();
			if(FenetreChat.isOuvert()) {
				StyledDocument doc = FenetreChat.getDoc();
				try {
					doc.insertString(doc.getLength(), "\n"+msgRecu.getMessage(), left );
				} catch (BadLocationException e) {
					e.printStackTrace();
				}
	            doc.setParagraphAttributes(doc.getLength(), 1, left, false);
			break;
			}
			
    	}
    	
    }
	
	
	
	
	/* type 0 : premier message de broadcast
	 * type, 1 : deuxieme message de broadcast avec pseudo
	 * type 2 : Message de déconnexion
	 * type 3 : message pseudo changé
	 */
	
	public void handleMessage(Message msg) throws IOException {
		switch(msg.getTypeMessage()) {
		
		//message broadcast sans pseudo pour signaler son arrivée 
		//action : répondre par un message avec notre pseudo 
		case 0 : 
			MessagePseudo msgPresentation = new MessagePseudo(this.localAddress, msg.getAddressSrc(), this.user.getPort(), msg.getPortSrc(), 1, this.user.getPseudo(), this.user.getId(), "");
			BOut.send(msgPresentation, msg.getAddressSrc(), msg.getPortSrc());
			break;
			
		//message broadcast avec pseudo 
		//action : on vérifie que le pseudo appartient à une personne active dans la liste du serveur et on l'ajoute dans la liste de la fenetre menu
		case 1 : 
			MessagePseudo msgPseudo = (MessagePseudo) msg;
			boolean pseudoExist = Request.sendPseudo(msgPseudo.getPseudo(), "pseudoOK");
			if(!pseudoExist) {
				Contact newUser = new Contact(msgPseudo.getPseudo(), msgPseudo.getAddressSrc(), msgPseudo.getPortSrc());
				if(FenetreMenu.ouvert) {
					FenetreMenu.getCb().addItem(newUser.getPseudo());
				}
			}
			break;
			
		//message de deconnexion
		//action : vérifier que le pseudo n'existe plus dans la liste des actifs du serveur et le supprimer de la liste de la fenetre menu
		//fermeture de la fenetre chat entre le user et la personne qui se deconnecte si elle est ouverte
		case 2 :
			MessagePseudo messageDeconnexion = (MessagePseudo) msg;
			boolean pseudoExiste = Request.sendPseudo(messageDeconnexion.getPseudo(), "pseudoOK");
			if(pseudoExiste){
				FenetreMenu.getCb().removeItem(messageDeconnexion.getPseudo());
				
			}
			if(FenetreChat.isOuvert()&&FenetreChat.getDest().getPseudo().equals(messageDeconnexion.getPseudo())) {
				FenetreChat.fermer();
			}
			break;
			
		//message pseudo changé
		//action : mettre à jour la liste des users de la fenetre pseudo avec le nouveau pseudo 
		case 3 : 
			MessagePseudo messageNewPseudo = (MessagePseudo) msg;
			this.open = false;
			 for(int i = 0 ; i<FenetreMenu.getCb().getItemCount() ; i++){
		         String pseudo = (String) FenetreMenu.getCb().getItemAt(i);
		         if(pseudo.equals(messageNewPseudo.getAncienPseudo())) {
		           	FenetreMenu.getCb().removeItem(pseudo);
		           	FenetreMenu.getCb().addItem(messageNewPseudo.getPseudo());
		         }
		      }		
		}
		
	}
	
	//Première connexion -> inscription
	public boolean connectInscription(String pseudo, String login) throws IOException, InterruptedException {
		
		//Envoi d'un message de broadcast sans pseudo  
		MessagePseudo msg_connexion = new MessagePseudo(this.localAddress, this.broadcast, this.user.getPort(), App.portDest, 0, "", this.user.getId(), "");
		BOut.send(msg_connexion, this.broadcast, App.portDest);
		
		//Attente pour avoir le temps de recevoir les réponses des users actifs
		Thread.sleep(2000);
		
		//On gère toutes les réponses reçues après notre broadcast
		ArrayList<MessagePseudo> msg_recus = BIn.getListMessage();
		for(int i =0; i<msg_recus.size(); i++) {
			handleMessage(msg_recus.get(i));
		}
		
		//Choix du pseudo
		boolean pseudoOK = choisirPseudo(pseudo);
		
		//Si le pseudo est acceptable :
		//Envoi d'un message de broadcast avec le pseudo 
		if(pseudoOK){
			Request.sendUser(pseudo, this.localAddress.toString(), Integer.toString(App.portSrc), "addUser");
			int id = db.addUser(pseudo, login);
			this.user.setId(id);
			MessagePseudo msgPresentation = new MessagePseudo(this.localAddress, this.broadcast, this.user.getPort(), App.portDest, 1, this.user.getPseudo(), this.user.getId(), "");
			BOut.send(msgPresentation, this.broadcast, App.portDest);
			this.user.setStatut("En ligne");
		}else {
			System.out.println("Echec de la connexion, veuillez choisir un autre pseudo");
		}
		return pseudoOK;
	}
	
	//Connexion (une inscription a déjà eu lieu)
	public boolean connectConnexion(String pseudo, String login) throws IOException, InterruptedException {
		
		//Envoi d'un message de broadcast sans pseudo  
		MessagePseudo msg_connexion = new MessagePseudo(this.localAddress, this.broadcast, this.user.getPort(), App.portDest, 0, "", this.user.getId(), "");
		BOut.send(msg_connexion, this.broadcast, App.portDest);
		
		//Attente pour avoir le temps de recevoir les réponses des users actifs
		Thread.sleep(2000);
		
		//On gère toutes les réponses reçues après notre broadcast
		ArrayList<MessagePseudo> msg_recus = BIn.getListMessage();
		for(int i =0; i<msg_recus.size(); i++) {
			handleMessage(msg_recus.get(i));
		}
		
		//Choix du pseudo
		boolean pseudoOK = choisirPseudo(pseudo);
		
		//Si le pseudo est acceptable :
		//Envoi d'un message de broadcast avec le pseudo 
		if(pseudoOK){
			Request.sendUser(pseudo, this.localAddress.toString(), Integer.toString(App.portSrc), "addUser");
			int id = db.getIdFromLogin(login);
			this.user.setId(id);
			MessagePseudo msgPresentation = new MessagePseudo(this.localAddress, this.broadcast, this.user.getPort(), App.portDest, 1, this.user.getPseudo(), this.user.getId(), "");
			BOut.send(msgPresentation, this.broadcast, App.portDest);
			db.updateStatus(id, "En ligne");
			this.user.setStatut("En ligne");
		}else {
			System.out.println("Echec de la connexion, veuillez choisir un autre pseudo");
		}
		return pseudoOK;
	}
	
	
	//Choix du pseudo
	public boolean choisirPseudo(String pseudo) throws IOException {
		
		//On demande au serveur si le pseudo peut être utilisé
		boolean pseudoExist = Request.sendPseudo(pseudo, "pseudoOK");
		
		if(pseudoExist){
			//Le pseudo est OK on l'attribue au user
			user.setPseudo(pseudo);
			return true;
		}else {
			System.out.println("Pseudo déjà utilisé");
			return false;
		}
	}
	
	//Déconnexion
	public void deconnexion() throws IOException {
		
		//On notifie le serveur de la deconnexion
		Request.sendDeconnexion(Integer.toString(this.user.getId()),this.user.getPseudo(), "deconnexion");
		
		//On envoie un message de deconnexion en broadcast
		MessagePseudo msgDeconnexion = new MessagePseudo(this.localAddress, this.broadcast, App.portSrc, App.portDest, 2, this.user.getPseudo(), this.user.getId(), "");
		BOut.send(msgDeconnexion, this.broadcast, App.portSrc);
		
		//Fermeture socket 
		this.BIn.interrupt();
        this.BOut.getSocket().close();
        this.server.interrupt();
		System.exit(1);
	}
	
	//Changement du pseudo 
	public boolean changerPseudo(String pseudo) throws IOException {
		
		String ancienPseudo = user.getPseudo();
		
		//Vérification que le nouveau pseudo est acceptable
        boolean changeOK = choisirPseudo(pseudo);
        
        //Si le changement est acceptable :
        //Changement du pseudo dans la base de donnée
        //Notification du serveur du changement
        //Envoi message de broadcast avec le nouveau pseudo
        if(changeOK){
            db.updatePseudo(this.user.getId(), pseudo);
            Request.sendPseudoChange(ancienPseudo, pseudo, "pseudoChanged");
            MessagePseudo msgNewPseudo = new MessagePseudo(this.localAddress, this.broadcast, App.portSrc, App.portDest, 3, pseudo, this.user.getId(), ancienPseudo);
            BOut.send(msgNewPseudo, this.broadcast, App.portSrc);
            return true;
        }else {
            return false;
        }
    }

	//Vérification que le chat en paramètre existe déjà ou non
    public boolean chatExists(int destID) {
        boolean chatExists = false;
        for(TCPChat tcp : listTCPDeg) {
            if(tcp.getDestID()==destID) {
                chatExists = true;
            }
        }
        return chatExists;
    }

    
    //Création d'un chat
    public void createChat (int destId, String destPseudo) throws IOException {
    	//Vérification de l'existence du chat
        boolean chatExists = chatExists(destId);
        
        //Si le chat n'existe pas encore
        if(!chatExists) {
        	
        	//Vérification que le destinataire est bien dans la liste des user actifs
        	boolean isNotOnList = Request.sendPseudo(destPseudo, "pseudoOK");
        	
        	//Si le destinataire est actif :
        	//Création d'un TCPChat 
        	if(!isNotOnList) {
        		Contact dest = Request.getUser(destPseudo, "getUser");
        		TCPChat connexionTCP = new TCPChat(dest, destId);
                listTCPDeg.add(connexionTCP);
                connexionTCP.addPropertyChangeListener(this);
        	}
        }
    }   
    
    
    public void addPropertyChangeListener(PropertyChangeListener pcl, String propertyName){
        this.support.addPropertyChangeListener(propertyName, pcl);
    }

    public void deletePropertyChangeListener(PropertyChangeListener pcl, String propertyName){
        this.support.removePropertyChangeListener(propertyName,pcl);
    }

	public Contact getUser() {
		return user;
	}

	public void setUser(Contact user) {
		this.user = user;
	}

	public DataBase getDb() {
		return db;
	}

	public void setDb(DataBase db) {
		this.db = db;
	}
	
	public ArrayList<TCPChat> getListeTCPChat(){
		return listTCPDeg;
	}
	
	public ArrayList<TCPChat> getListeTCPChat2(){
		return listTCPOk;
	}
	
	public TCPChat getTCPChat() {
		return this.listTCPDeg.remove(0);
	}
	
	public boolean getOpen() {
		return this.open;
	}
	
	public void setOpen(boolean res) {
		this.open = res;
	}
	
}
