package controller;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;

import javax.swing.JOptionPane;

import model.Chat;
import model.LocalUser;
import model.Message;
import model.Msg_Text;
import model.RemoteUser;
import view.Interface;

public class Controller {

	/*** CONSTANTES ***/
	final static int portUDPlistening_remoteUsr2 = 31002; // TO REMOVE when we use broadcast
    final static int portUDPlistening_remoteUsr3 = 31003; // TO REMOVE when we use broadcast
    
    public Boolean interfaceRunning;
	/*** ATTRIBUTS ***/
	protected LocalUser myUser;
	protected Interface view;
	private ListeningThreadUDP udp_connect_thread;
	private ListeningThreadTCPConnection tcp_connect_thread;
	private Historique histoire;
	private Chat activeChat;
	
	/**
	 *  Constructor of Controller
	 * @parametres
	 * 		@param portUDPsend : int => le numéro de port pour envoyé ces informations lors d'un changements ou d'une nouvelle connexion
	 * 		@param portUDPlistening : int => le numéro de port pour recevoir les informations des nouveaux utilisateurs ou les changements
	 * 		@param portTCP : int => le numéro de port pour commencer une nouvelle conversation
	 * @descrition
	 * <p>
	 * 		On récupère l'adresse de la machine, on demande un pseudo à l'utilisateur que l'on vérifie
	 * 		 Une fois validé l'utilisateur devient actif :
	 * 			- écoute UDP pour les pseudos
	 * 			- écoute TCP pour de nouvelles conversation
	 * 		    - notification aux autres utilisateurs actifs
	 * </p>
	 */
	private Controller(int portUDPsend,int portUDPlistening,int portTCP,Historique histoire) {
		this.histoire= histoire;
		
		InetAddress addIP = null;
		try
		{
			addIP = InetAddress.getLocalHost();
		}
		catch(UnknownHostException e) {
			JOptionPane.showMessageDialog(null ,"Could not find local address!");
		}
		this.myUser = new LocalUser("Unknown",addIP,portUDPsend,portUDPlistening,portTCP);

		try {
			this.myUser.setPseudo(this.initPseudo());
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		this.udp_connect_thread = new ListeningThreadUDP("UDP Listening thread",this);
		this.udp_connect_thread.start();
		
		this.tcp_connect_thread = new ListeningThreadTCPConnection("TCP main Listening thread",this);
		this.tcp_connect_thread.start();
		
		notify_remote_users();
		
		
	}
	
	/**
	 *  Constructor of Controller
	 * @parametres
	 * 		@param portUDPsend : int => le numéro de port pour envoyé ces informations lors d'un changements ou d'une nouvelle connexion
	 * 		@param portUDPlistening : int => le numéro de port pour recevoir les informations des nouveaux utilisateurs ou les changements
	 * 		@param portTCP : int => le numéro de port pour commencer une nouvelle conversation
	 * @descrition
	 * <p>
	 * 		On récupère l'adresse de la machine, on demande un pseudo à l'utilisateur que l'on vérifie
	 * 		 Une fois validé l'utilisateur devient actif :
	 * 			- écoute UDP pour les pseudos
	 * 			- écoute TCP pour de nouvelles conversation
	 * 		    - notification aux autres utilisateurs actifs
	 * </p>
	 */
	private Controller(int portUDPsend,int portUDPlistening,int portTCP,String pseudo,Historique histoire) {
		this.histoire=histoire;
		InetAddress addIP = null;
		try
		{
			addIP = InetAddress.getLocalHost();
		}
		catch(UnknownHostException e) {
			JOptionPane.showMessageDialog(null ,"Could not find local address!");
		}
		this.myUser = new LocalUser(pseudo,addIP,portUDPsend,portUDPlistening,portTCP);
		
		this.udp_connect_thread = new ListeningThreadUDP("UDP Listening thread",this);
		this.udp_connect_thread.start();
		
		this.tcp_connect_thread = new ListeningThreadTCPConnection("TCP main Listening thread",this);
		this.tcp_connect_thread.start();
		
		notify_remote_users();
	}
	
	
	
	
	/*** GETTERS ***/
	public LocalUser getMyUser() {
		return myUser;
	}
	public Interface getview() {
		return view;
	}
	public ListeningThreadUDP getUdp_connect_thread() {
		return udp_connect_thread;
	}
	public ListeningThreadTCPConnection getTcp_connect_thread() {
		return tcp_connect_thread;
	}
	public Historique getHistory() {
		return histoire;
	}
	
	/*** SETTERS ***/
	public void setMyUser(LocalUser myUser) {
		this.myUser = myUser;
	}
	public void setview(Interface view) {
		this.view = view;
	}
	public void setUdp_connect_thread(ListeningThreadUDP udp_connect_thread) {
		this.udp_connect_thread = udp_connect_thread;
	}
	public void setTcp_connect_thread(ListeningThreadTCPConnection tcp_connect_thread) {
		this.tcp_connect_thread = tcp_connect_thread;
	}
	public void setHistory(Historique histoire) {
		this.histoire=histoire;
	}
	
	
	/**
	 * <p>
	 * 		Demande à l'utilisateur de rentrer un pseudo et valide de ce dernier en demandant aux 
	 * 		utilisateurs distants leurs informations
	 * </p>
	 */
	public String initPseudo() throws IOException {
	    String tmpPseudo = JOptionPane.showInputDialog(null, "Enter nickname:");  // Read user input

		while(!(this.validatePseudo(tmpPseudo))) { // On demande aux autres utilisateurs de nous envoyer leurs informations et on test si le pseudo est déjà utilisé
		    tmpPseudo = JOptionPane.showInputDialog(null, "Enter another nickname:");  // Read user input
		}
		
		//sc1.close();
		JOptionPane.showMessageDialog(null ,"Your nickname : " + tmpPseudo + " is valid !");
		
		return tmpPseudo;
	}
	
	/**
	 * <p>
	* 		Demande à l'utilisateur de rentrer un pseudo et valide de ce dernier en demandant aux 
	* 		utilisateurs distants leurs informations.
	* 		On regarde si le pseudo est bien différent de l'ancien
	* </p>
	*/
	public void changePseudo() throws IOException { 
	    String oldPseudo = this.myUser.getPseudo(); //Saves the old one for comparison

	    String tmpPseudo = view.PseudotextField.getText();  // Read user input

	    while(!(this.validatePseudo(tmpPseudo)) || tmpPseudo.equals(oldPseudo)) {
	        view.Pseudolabel.setText("Already exists, enter another nickname. Your current username is: " + oldPseudo);  // Read user input
	        tmpPseudo = view.PseudotextField.getText();  // Read user input
	    }

	    this.myUser.setPseudo(tmpPseudo);

	    JOptionPane.showMessageDialog(null ,"Your new nickname : " + tmpPseudo + " is valid !");

	    this.notify_remote_users();
	}
	/**
	 * <p>
	 * 		*tmpPseudo : String => Le pseudo à valider
	 *</p><p>
	 * 		Envoi en broadcast (pour l'instant envoi sur les ports de notre ordinateur) d'une demande d'information 
	 *</p><p>
	 * 		On attend les réponses pendant 5 secondes
	 *</p><p>
	 * 		On valide le pseudo en fonction des pseudos reçu
	 *</p><p>
	 * 		On en profite pour ajouter les utilisateurs répondant à notre liste d'utilisateur distant (RemoteUser)
	 * </p>
	 */
	public Boolean validatePseudo(String tmpPseudo) throws IOException {

		
		// Call broadcast 
		InetAddress broadcastIP = InetAddress.getLocalHost(); // change to broadcast
		//System.out.println(broadcastIP);
		DatagramSocket dgramSocket = new DatagramSocket(this.myUser.getPortUDPsend(),this.myUser.getAddIP());
		byte[] buffer = new byte[256];
		
		// Création du message à envoyer
		String toSend = this.myUser.getAddIP()+":"+this.myUser.getPortUDPsend()+":test";
		
		// Send to remote user 1
		DatagramPacket outPacket= new DatagramPacket(toSend.getBytes(), toSend.length(),broadcastIP, portUDPlistening_remoteUsr2);
		dgramSocket.send(outPacket);
		
		// Send to remote user 2
		outPacket= new DatagramPacket(toSend.getBytes(), toSend.length(),broadcastIP, portUDPlistening_remoteUsr3);
		dgramSocket.send(outPacket);
		
		// Initialisation des variables de la boucle
		Boolean valid = true;
	
        DatagramPacket inPacket;
        String response = null;
        String[] tabresponse= new String [4];
		dgramSocket.setSoTimeout(1000);
		Boolean arecu;
		
		System.out.print("Wait for pseudo validation ...\n");

		int nbReponse =0;
		Date oldDate = new Date();
        Date newDate = new Date();
		while( (newDate.getTime()-oldDate.getTime()) < 5000 && valid) {
			
			nbReponse++;
			inPacket= new DatagramPacket(buffer, buffer.length);
			
			arecu=true;
			try{
				dgramSocket.receive(inPacket);
			}catch (Exception e) {
				arecu=false;
			} 
			buffer = inPacket.getData();
			response = new String(buffer);
			
			if(arecu) {				
				// On découpe la réponse en tableau de string ([adresseIP,tcpPort,nickname])
				tabresponse = response.split(":");
				
				// Affichage de la réponse
				//System.out.println("Remote user n°"+nbReponse+"\nIP : "+tabresponse[0]+"\nn°Port : "+tabresponse[1]+"\npseudo : "+tabresponse[2]);
				
				// Si reception on ajoute l'utilisateur à notre liste d'utilisateur distant
				this.myUser.addRemoteUser(InetAddress.getByName(tabresponse[0].split("/")[1]),Integer.parseInt(tabresponse[1]),tabresponse[2]);
				valid= (tmpPseudo.compareTo(tabresponse[2])!=0); // On regarde la différence entre notre pseudo et le pseudo reçu
				
			}
			
	        newDate = new Date();
		}
		dgramSocket.close();
		
		if(!valid) {
			JOptionPane.showMessageDialog(null ,"Nickname : "+tmpPseudo +" is taken !");
		}
		
		return valid;
	}
	/** notify_remote_users
	 * <p>
	 * 		En utilisant le port UDP d'envoi, on envoie en broadcast les informations nous concernant
	 * </p>
	 */
	public void notify_remote_users() {
		// Création du socket d'envoi d'information
		DatagramSocket dgramSocket= null;
        try {
            dgramSocket= new DatagramSocket(this.myUser.getPortUDPsend(),this.myUser.getAddIP());
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Construction du message à envoyer
        String toSend = this.myUser.getAddIP().toString()+":"+this.myUser.getPortTCP()+":"+this.myUser.getPseudo()+":test";
        
        DatagramPacket outPacket =null;
        
        // Send information to usr2
        if(this.myUser.getPortUDPlistening()!=portUDPlistening_remoteUsr2) {
        	outPacket = new DatagramPacket(toSend.getBytes(), toSend.length(),this.myUser.getAddIP(), portUDPlistening_remoteUsr2);
            try {
                dgramSocket.send(outPacket);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        
        // Send information to usr3
        if(this.myUser.getPortUDPlistening()!=portUDPlistening_remoteUsr3) {

	        outPacket= new DatagramPacket(toSend.getBytes(), toSend.length(),this.myUser.getAddIP(), portUDPlistening_remoteUsr3);
	        try {
	            dgramSocket.send(outPacket);
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
        }
        dgramSocket.close();
	}


	public Chat openSession(RemoteUser rm) {
		Chat c = myUser.addChats(rm); // Create chat and add it to myUser
		
		/*** Create socket send => ask connection to server of rm ***/
		Socket link=null;
		try {
			System.out.println("("+this.myUser.getPseudo()+") Connecting to "+c.getRemoteUser().getPortTCP()+" of " + c.getRemoteUser().getPseudo());
			link=new Socket(c.getRemoteUser().getAddIP(),c.getRemoteUser().getPortTCP()/*, InetAddress.getLocalHost() ,myUser.getPortTCP()*/);
		}catch(IOException e) {
			System.out.println("Error linking to TCP server of "+ c.getRemoteUser().getPortTCP());
		}
		c.setSocket(link);
	    
	    /*** recup history and put it in model ***/
		/*
	    try {
	        System.out.println(this.getHistory().retrieveMessage(getMyUser(), c.getRemoteUser()));
	        String history = this.getHistory().retrieveMessage(getMyUser(), c.getRemoteUser());
		} catch (SQLException e) {
			System.out.println("souci avec le retrieveMsg");
			e.printStackTrace();
		}
	    */
	    return c;
	}
	public String askOpenSession(int index) {
		String history="";
		RemoteUser rm = myUser.getRemoteUsersList().get(index);
		Chat c; 
		
		/*** Recup or create chat ***/
		if(myUser.getChatIndexOf(rm)!=-1) { // if already a chat open
			c = myUser.getChats().get(myUser.getChatIndexOf(rm));
			JOptionPane.showMessageDialog(null ,"Active session with "+c.getRemoteUser().getPseudo()+" loading history");
		}
		else {	// else create it
			c=openSession(rm);
			JOptionPane.showMessageDialog(null ,"New session with "+c.getRemoteUser().getPseudo());
		}
		
		// Look for history
		int nbMessage = c.getMessages().size();
		for(int i=0;i<nbMessage;i++) {
			history+="<br>"+c.getMessages().get(i).getMessage();
		}			
		this.activeChat = c;
		
		return history;
	}
	
	public void closeSession(Chat c) {
		sendMessage(new Msg_Text(myUser.getAddIP(),"end"), c); // tell rm session STOP
		JOptionPane.showMessageDialog(null ,"Close session with "+c.getRemoteUser().getPseudo()+" loading history"); 
		this.myUser.closeChat(c);
	}
	
	public void askToSend(String textMessage){
		sendMessage(new Msg_Text(myUser.getAddIP(),textMessage), this.activeChat);
	}
	public void sendMessage(Message msg,Chat c) {
		/*** Init send process ***/
		PrintWriter out=null;
		try {
			out = new PrintWriter(c.getUserSocket().getOutputStream(),true);
		} catch (IOException e) {
			e.printStackTrace();
		}
		c.addMessage(msg);

		/*** Gestion des différentes instance de Message ***/
		// TODO check if instance of Msg_Text or anything else and threat it given a type
		String message = (String) msg.getMessage();

		// Date l'envoie du message
		DateFormat dateFormat = new SimpleDateFormat("yyyy MM dd HH mm ss");
		Date date=new Date();
		
	    // Sauvegarde dans la base de données
	    //this.getHistory().saveMessage(getMyUser(), c.getRemoteUser(),message ,dateFormat.format(date));
	    
	    // Send message
		out.println(dateFormat.format(date));
		out.println(message);
		
	}
	
	
	
	
	
	public static void main(String[] args) throws IOException, InterruptedException {
		
		System.out.println("start program");

		Historique histoire=new Historique();
		
		/*************************       INIT          *******************************/
		/** Création des utilisateurs **/
		// REMOTEUSER_1 - MIKE
		Controller ctr2 = new Controller(31012,portUDPlistening_remoteUsr2,31022,"Mike",histoire); 
		// REMOTEUSER_2 - ALICE
		Controller ctr3 = new Controller(31013,portUDPlistening_remoteUsr3,31023,"Alice",histoire); 
		// LOCAL USER
		Controller ctr1 = new Controller(31011,31001,31021,histoire); 

		
		
		/*********************** 		TEST AREA  				********************/
		/** Simulation of a session 1 message **/
		/*
		// AFFICHAGE REMOTE USER CHOISIE
		System.out.println("("+ctr1.myUser.getPseudo()+" ) OPEN SESSION WITH "+ctr1.myUser.getRemoteUsersList().get(0).getPseudo());
		// SELECTION DE L UTILISATEUR
		RemoteUser rm0 = ctr1.myUser.getRemoteUsersList().get(0);
		// OPEN SESSION
		ctr1.openSession(rm0); 
		// RECUPERATION DE LA CONVERSATION
		Chat chatwithrm0 = ctr1.myUser.getChats().get(ctr1.myUser.getChatIndexOf(rm0));
		// SEND MESSAGE
		ctr1.sendMessage(new Msg_Text(ctr1.myUser.getAddIP(),"test"), chatwithrm0);
		// CLOSE SESSION
		ctr1.closeSession(chatwithrm0); 
		*/
		
		/** Unused function **/
		// MANUAL SELECTION OF ACTIVE USER
		//ctr1.selectActiveUser(); 
		// CHANGE USER NICKNAME
		//ctr1.changePseudo(); 
					
		
		
		/*************************       LOOP          *******************************/

		/** Création de l'interface graphique **/ 
		ctr1.interfaceRunning =true;
		ctr1.view=Interface.createAndShowGUI(ctr1);	
		
		/** loop **/
		while(ctr1.interfaceRunning) {
			//ctr1.view.update(info)
		}
		
		System.out.println("Fin de la boucle");
		
		
		
		/************************ 		 END				*****************************/
		
		/** Close thread and socket **/
		// REMOTEUSER_1 - MIKE
		ctr2.myUser.closeAllChat();
		ctr2.tcp_connect_thread.close();
		ctr2.udp_connect_thread.close();
		// REMOTEUSER_2 - ALICE
		ctr3.myUser.closeAllChat();
		ctr3.tcp_connect_thread.close();
		ctr3.udp_connect_thread.close();
		// LOCAL USER
		ctr1.myUser.closeAllChat();
		ctr1.tcp_connect_thread.close();
		ctr1.udp_connect_thread.close();
		// AFFICHAGE
		System.out.println("end program");
		JOptionPane.showMessageDialog(null ,"END");
	}
	
	
	
	
	
	/********************* Fonction debug console => A mettre dans l'interface ******************************************/
	
	/**
	 * <p>
	 * Affichage de la liste d'utilisateurs actifs avec leurs index dans la liste
	 * </p>
	 */
	public void printRemoteUserList() {
		System.out.println("Internal list of active remote users:");
		
        for(int i=0; i<this.myUser.getRemoteUsersList().size(); i++) {
        	System.out.println("- ("+i+") Username: " + this.myUser.getRemoteUsersList().get(i).getPseudo());
        }
    }
	
	/**
	 * <p>
	 * Laisse l'utilisateur choisir parmis la liste d'utilisateurs actifs celui avec lequel il souhaite échanger via un chat
	 *</p> 
	 */
	public void selectActiveUser() throws IOException {
		this.printRemoteUserList();
        int index=Integer.parseInt(JOptionPane.showInputDialog(null, "Please, enter index of one active user that you saw on the list to start a conversation with:"));
        
        if (index >= 0 && index<this.myUser.getRemoteUsersList().size()) {
        	
            if(this.myUser.getChatIndexOf(this.myUser.getRemoteUsersList().get(index))==-1){
            	JOptionPane.showMessageDialog(null ,"User "+this.myUser.getRemoteUsersList().get(index).getPseudo()+" is already in chat with you");
            }
            else {
            	this.openSession(myUser.getRemoteUsersList().get(index));
            }
        }
        else {
        	JOptionPane.showMessageDialog(null ,"Wrong index (no active at index number "+index+" )");
        	this.selectActiveUser();
        }
    }
	
	/*************************************************************************************************************************/


}
