package com.softgraf.chat.cliente;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.LinkedHashSet;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;

import com.softgraf.chat.Mensagem;


public class ChatCliente extends JFrame {

	private static final long serialVersionUID = 6925857407118503230L;
	
	// variáveis estáticas da classe
	private static final Color COR_AREA_MENSAGEM;
	private static final Color COR_AREA_USUARIOS;
	private static LinkedHashSet<String> listaUsuariosOnline;
	private static JTextArea areaMensagem, areaUsuarios;
	private static JPanel painel;
	private static JTextField campo;
	private static JButton botaoEnviar;
	private static JComboBox<String> comboUsuarios;
	private static boolean conectado;

	// variáveis do objeto
	private String nomeUsuario;
	private String idCliente;  // nome + " " + IP 
	private Socket servidor;
	private ServerSocket cliente;
	private ObjectInputStream entradaServidor;
	private ObjectOutputStream saidaServidor;
	
	// gerencia a comunicação com o servidor após a conexão inicial
	private Thread atualizar;
	
	// inicializador estático
	static {
		COR_AREA_MENSAGEM = new Color(200, 255, 200);
		COR_AREA_USUARIOS = new Color(180, 180, 255);
		listaUsuariosOnline = new LinkedHashSet<String>();
	}
	
	// construtor
	public ChatCliente() {
		inicializaInterface();
		iniciaComunicacaoServidor();
		recebeMensagens();
	}

	private void inicializaInterface() {
		setSize(800, 600);
		setLocationRelativeTo(null);
		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		
		addComponentListener(new ComponentAdapter() {
			@Override
			public void componentResized(ComponentEvent e) {
				redimensionaComponentes();
			}
		});

		nomeUsuario = JOptionPane.showInputDialog(null, "Seu nome",	"Nome do Usuário", JOptionPane.QUESTION_MESSAGE);
		if (nomeUsuario == null || nomeUsuario.trim().isEmpty()) {
			System.exit(0);
		}
		nomeUsuario = nomeUsuario.trim();

		areaMensagem = new JTextArea("Chat iniciado.\n\n");
		areaMensagem.setBackground(COR_AREA_MENSAGEM);
		areaMensagem.setBorder(BorderFactory.createLoweredBevelBorder());
		areaMensagem.setEditable(false);
		areaMensagem.setHighlighter(null);

		areaUsuarios = new JTextArea();
		areaUsuarios.setBackground(COR_AREA_USUARIOS);
		areaUsuarios.setBorder(BorderFactory.createLoweredBevelBorder());
		areaUsuarios.setPreferredSize(new Dimension(300, areaUsuarios.getHeight()));
		areaUsuarios.setEditable(false);
		areaUsuarios.setHighlighter(null);
		mostraUsuariosOnline();

		campo = new JTextField();
		botaoEnviar = new JButton("Enviar para");
		botaoEnviar.setEnabled(false);
		botaoEnviar.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				
				String idUsuario = (String) comboUsuarios.getSelectedItem();
				String[] lista = idUsuario.split(" ");
				String ip = lista[lista.length - 1];  // IP ou "<<TODOS>>"
				
				if (ip.equalsIgnoreCase("<<TODOS>>"))
					enviarMensagemParaTodos();
				else
			        enviarMensagem(ip);
			}
		});
		comboUsuarios = new JComboBox<String>();
		comboUsuarios.setEnabled(false);
		comboUsuarios.setEditable(false);
		redimensionaComponentes();

		painel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0));
		painel.add(campo);
		painel.add(botaoEnviar);
		painel.add(comboUsuarios);

		getContentPane().add(areaMensagem, BorderLayout.CENTER);
		getContentPane().add(areaUsuarios, BorderLayout.EAST);
		getContentPane().add(painel, BorderLayout.SOUTH);

		setVisible(true);
	}

	// cria a conexao com o servidor
	private void iniciaComunicacaoServidor() {
		try {
			
			// conexão com o servidor na porta 7001
			servidor = new Socket("192.168.1.9", 7001);
			idCliente = nomeUsuario  + " " + InetAddress.getLocalHost().getHostAddress();
			setTitle("Chat Cliente - Usuário: " + idCliente);
			
			saidaServidor = new ObjectOutputStream(servidor.getOutputStream());
			Mensagem msgSaida = new Mensagem(idCliente, Mensagem.CONECTAR, "Estou chegando!");
			saidaServidor.writeObject(msgSaida);
			saidaServidor.flush();

			entradaServidor = new ObjectInputStream(servidor.getInputStream());
			Mensagem msgEntrada = (Mensagem) entradaServidor.readObject();
			String idServidor = msgEntrada.getIDusuario();
			String msg = msgEntrada.getMensagem();
			areaMensagem.append(idServidor + " respondeu: " + msg);
			
			// cria uma thread para sincronizar os usuários online com o servidor
			atualizar = new AtualizaClientesOnline(entradaServidor, saidaServidor, idCliente);

			// faz solicitação de clientes online para o servidor a cada 10 segundos
			atualizar.start();

		} catch (UnknownHostException e) {
			JOptionPane.showMessageDialog(null, e.getMessage(), "ChatCliente Erro Fatal 1", JOptionPane.ERROR_MESSAGE);
			System.exit(0);

		} catch (IOException e) {
			JOptionPane.showMessageDialog(null, "Falha de conexão com o servidor!", "ChatCliente Erro Fatal 2", JOptionPane.ERROR_MESSAGE);
			System.exit(0);

		} catch (ClassNotFoundException e) {
			JOptionPane.showMessageDialog(null, e.getMessage(), "ChatCliente Erro Fatal 3", JOptionPane.ERROR_MESSAGE);
			System.exit(0);

		}

	}

	
	 // recebe mensagens de outros usuários na porta 3001
	private void recebeMensagens() {
		conectado = true;
		Socket usuario;
		ObjectInputStream entrada;
		
		try {
			cliente = new ServerSocket(3001);
	
			while (conectado){ 
				
				usuario = cliente.accept();
				entrada = new ObjectInputStream(usuario.getInputStream());
				Mensagem msgEntrada = (Mensagem) entrada.readObject();
				String nome = msgEntrada.getNomeUsario();
				String msg = msgEntrada.getMensagem();
				areaMensagem.append("\n" + nome + " diz: " + msg);
				entrada.close();
				usuario.close();
				
			}
			
			
		} catch (IOException e) {  // lança exceção quando fechado o programa
			if (conectado)         // não mostrar mensagem
				JOptionPane.showMessageDialog(null, e.getMessage(), "ChatCliente Erro 4", JOptionPane.ERROR_MESSAGE);
			
		} catch (ClassNotFoundException e) {
			JOptionPane.showMessageDialog(null, e.getMessage(), "ChatCliente Erro 5", JOptionPane.ERROR_MESSAGE);
		} 
		
	}
	
	private void redimensionaComponentes() {
		if (this.isVisible()) {
			int largJanela = this.getWidth();
			int largCampo = (int) (largJanela * 0.65);
			int largCombo = largJanela - (largCampo + 135);
			campo.setPreferredSize(new Dimension(largCampo, 30));
			botaoEnviar.setPreferredSize(new Dimension(100, 30));
			comboUsuarios.setPreferredSize(new Dimension(largCombo, 30));
			painel.updateUI();
		}
	}
	
	@Override
	public void dispose() {
		conectado = false;
		Mensagem msg = new Mensagem(idCliente, Mensagem.DESCONECTAR, "Fui!");
		atualizar.interrupt();  // encerra a execução da thread de atualização
		
		try {
			
			// envia aviso de desconexão para o servidor
			saidaServidor.writeObject(msg);
			saidaServidor.flush();
			
			// fecha fluxos de entrada e saída
			entradaServidor.close();
			saidaServidor.close();

			// fecha sockets
			servidor.close();	
			cliente.close();
			
			
		} catch (IOException e) {
			JOptionPane.showMessageDialog(null, e.getMessage(), "ChatCliente Erro 6", JOptionPane.ERROR_MESSAGE);
		}
		
		super.dispose();
	}
	
	// exibe todos os usuários online em areaUsuarios
	protected synchronized static void mostraUsuariosOnline() {
		Iterator<String> cursor = listaUsuariosOnline.iterator();
		areaUsuarios.setText(" ============== Usuários online ============ ");
		while (cursor.hasNext()) {
			areaUsuarios.append("\n\n" + (String) cursor.next());
		}
	}
	
	// substitui a lista de usuários online
	protected synchronized static void setUsuariosOnline(LinkedHashSet<String> lista){
		listaUsuariosOnline = lista;
	}
	

	protected synchronized static void atualizaComboUsuarios(){
		
		if (listaUsuariosOnline.size() > 0){

		    // verifica se listaUsuariosOnline é diferente do conteúdo de comboUsuarios
			int totalItens = comboUsuarios.getItemCount() - 1;  // menos "<<TODOS>>"
			String itemCombo;
			boolean encontrado = false;
			
			if (totalItens == listaUsuariosOnline.size()) { // nenhum usuario entrou ou saiu
				// procura itemCombo em listaUsuariosOnline
				for (String idUsuario : listaUsuariosOnline) {
					encontrado = false;
					
					for (int i=1; i <= totalItens; i++){  
						itemCombo = comboUsuarios.getItemAt(i);
						if (itemCombo.equalsIgnoreCase(idUsuario)) {
							encontrado = true;
							break;
						}
					}
					
					// para a busca se apenas um itemCombo não for encontrado
					if (!encontrado)
						break;
				}
				
				if (encontrado)
				   return;   // não precisa atualizar, nenhum usuário entrou ou saiu do chat
			}
		
			
			//  habilita componentes
			botaoEnviar.setEnabled(true);
			comboUsuarios.setEnabled(true);
			comboUsuarios.removeAllItems();
			comboUsuarios.addItem("<<TODOS>>");
			
			// adiciona usuário ao combo
			for (String idUsuario : listaUsuariosOnline) {
				comboUsuarios.addItem(idUsuario);
			}
			
		} else {
			// desabilita componentes
			botaoEnviar.setEnabled(false);
			comboUsuarios.setEnabled(false);
			comboUsuarios.removeAllItems();
		}
	}
	
	private void enviarMensagem(String ip){
		String msg = campo.getText().trim();
		
		if (!msg.isEmpty()){
			
			try {
				Socket usuario = new Socket(ip, 3001);  // cria conexao com outro cliente 
				ObjectOutputStream saida = new ObjectOutputStream(usuario.getOutputStream());
				Mensagem mensagem = new Mensagem(idCliente, Mensagem.ENVIAR_MSG, msg);
				saida.writeObject(mensagem);
				saida.close();
				usuario.close();
				
			} catch (UnknownHostException e) {
				JOptionPane.showMessageDialog(null, e.getMessage(), "ChatCliente Erro 7", JOptionPane.ERROR_MESSAGE);
				
			} catch (IOException e) {
				JOptionPane.showMessageDialog(null, e.getMessage(), "ChatCliente Erro 8", JOptionPane.ERROR_MESSAGE);
			}
			
			campo.setText("");
		}
	}
	
	private void enviarMensagemParaTodos(){
		String msg = campo.getText().trim();
		Socket usuario;
		ObjectOutputStream saida;
		Mensagem mensagem;
		
		if (!msg.isEmpty()){
			
			try {
				
				for (String idUsuario : listaUsuariosOnline) {
					
					String[] lista = idUsuario.split(" ");
					String ip = lista[lista.length - 1];
					usuario = new Socket(ip, 3001);
					saida = new ObjectOutputStream(usuario.getOutputStream());  // cria conexao com outro cliente 
					mensagem = new Mensagem(idCliente, Mensagem.ENVIAR_MSG, msg);
					saida.writeObject(mensagem);
					saida.close();
					usuario.close();
				}
					
			} catch (UnknownHostException e) {
				JOptionPane.showMessageDialog(null, e.getMessage(), "ChatCliente Erro 9", JOptionPane.ERROR_MESSAGE);
				
			} catch (IOException e) {
				JOptionPane.showMessageDialog(null, e.getMessage(), "ChatCliente Erro 10", JOptionPane.ERROR_MESSAGE);
			}

			campo.setText("");
		}
	}
	
	public static void main(String[] args) {
		new ChatCliente();
	}
}
