import java.io.*;
import java.net.*;
import javax.swing.*;



/**
 * Clase que define los parametros necesarios para implementar un cliente que se pueda
 * conectar con un servidor.
 * @author José Antonio Silva, Mario Ungemach, Jorge Mellings
 */
public class Client  extends Thread{

  /**
 * objeto utilizado para escribir datos hacia el servidor.
 */
   private static ObjectOutputStream salida;
/**
 * objeto utilizado para recibir datos desde el servidor.
 */
   private static ObjectInputStream entrada;

 /**
 * objeto utilizado para enviar mensajes hacia el servidor.
 */
   private static String mensaje = "";
/**
 * objeto utilizado para implementar un socket con el que se comunica con el servidor,
 * a traves del cual poder enviar y recibir mensajes.
 */
   private static Socket cliente;
    /**
 * objeto utilizado para implementar un socket con el que se comunica con el servidor
 *  a traves del cual poder enviar y recibir archivos.
 */
   private static Socket archivo;


/**
 * Constructor de la clase Client.
 */
   public Client( ) // inicializar cliente.
   {

   } // fin del constructor de Cliente


    /**
 *Metodo para invocar el metodo "pide(f)" que recibe el fichero desde el servidor.
 *@param f String que contiene el nombre que tendra el fichero que se reciba.
 */
       public static void pedir(String f) {
      pide(f);
    }

 /**
 *Metodo para invocar el metodo "enviaFichero(f)" que envia el fichero hacia el servidor.
 *@param f String que contiene el nombre que tendra el fichero que se enviara
 */
    public static void env(String f){
        try {
            enviaFichero(f);
        } catch (Exception e)        {
            e.printStackTrace();
        }

   }

 /**
 *Metodo para enviar el fichero hacia el servidor.
 *@param fichero String que contiene el nombre del fichero a enviar.
 */
 public static void enviaFichero(String fichero)
    {
        try
        {
         // conectarAServidor(JSecureTransfer.jTextField3.getText(), Integer.parseInt(JSecureTransfer.jTextField4.getText())+1);
            archivo = new Socket(JSecureTransfer.jTextField3.getText(), Integer.parseInt(JSecureTransfer.jTextField4.getText())+1);
            boolean enviadoUltimo=false;
            ObjectOutputStream oos = new ObjectOutputStream(cliente.getOutputStream());

            // Se abre el fichero.
            FileInputStream fis = new FileInputStream(fichero);

            // Se instancia y rellena un mensaje de envio de fichero
            Fichero f_env = new Fichero();
            f_env.nombreFichero = fichero;

            // Se leen los primeros bytes del fichero en un campo del mensaje
            int leidos = fis.read(f_env.contenidoFichero);

            // Bucle mientras se vayan leyendo datos del fichero
            while (leidos > -1)
            {

                // Se rellena el número de bytes leidos
                f_env.bytesValidos = leidos;

                // Si no se han leido el máximo de bytes, es porque el fichero
                // se ha acabado y este es el último mensaje
                if (leidos < Fichero.LONGITUD_MAXIMA)
                {
                    f_env.ultimoMensaje = true;
                    enviadoUltimo=true;
                }
                else
                    f_env.ultimoMensaje = false;

                // Se envía por el socket

                oos.writeObject(f_env);
                 System.out.println("enviando");
                // Si es el último mensaje, salimos del bucle.
                if (f_env.ultimoMensaje)
                    break;

                // Se crea un nuevo mensaje
                f_env = new Fichero();
                f_env.nombreFichero = fichero;

                // y se leen sus bytes.
                leidos = fis.read(f_env.contenidoFichero);
            }

            if (enviadoUltimo==false)
            {
                f_env.ultimoMensaje=true;
                f_env.bytesValidos=0;
                oos.writeObject(f_env);
            }
            // Se cierra el ObjectOutputStream
          oos.close();
         archivo.close();
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }

   /**
 *Metodo para recibir el fichero desde el servidor.
 *@param fichero String que contiene el nombre que tendra el fichero que se reciba.
 */
 public static void pide(String fichero)
    {
        try
        {

          archivo = new Socket(JSecureTransfer.jTextField3.getText(), Integer.parseInt(JSecureTransfer.jTextField4.getText())+1);

            FileOutputStream fos = new FileOutputStream(fichero);

            // Se crea un ObjectInputStream del socket para leer los mensajes
            // que contienen el fichero.
            ObjectInputStream ois = new ObjectInputStream(archivo
                    .getInputStream());
            Fichero mensajeRecibido;
            Object mensajeAux;
            do
            {
                // Se lee el mensaje en una variabla auxiliar
                mensajeAux = ois.readObject();

                // Si es del tipo esperado, se trata
                if (mensajeAux instanceof Fichero)
                {
                    mensajeRecibido = (Fichero) mensajeAux;
  
                    fos.write(mensajeRecibido.contenidoFichero, 0,
                            mensajeRecibido.bytesValidos);
                    System.out.print("listo");
                } else
                {
                    // Si no es del tipo esperado, se marca error y se termina
                    // el bucle
                    System.err.println("Mensaje no esperado "
                            + mensajeAux.getClass().getName());
                    break;
                }
            } while (!mensajeRecibido.ultimoMensaje);

            // Se cierra socket y fichero
      fos.close();
        ois.close();
          archivo.close();

        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }

 /**
 *Metodo para configurar y ejecutar el cliente.
 *@param host IP del servidor.
 *@param puerto Numero de puerto que esta usando el servidor para escuchar peticiones.
 */
   public static void ejecutarCliente( String host, int puerto) throws IOException // conectarse al servidor y procesar mensajes del servidor
   {
      // conectarse al servidor, obtener flujos, procesar la conexion
     try {
         conectarAServidor(host, puerto); // PPrimero se crea un socket para realizar la conexion
         obtenerFlujos();      //Se obtenienen flujos de entrada y salida.
         procesarConexion(); //Se procesa la conexion.
      }
      // el servidor cerro la conexion
     catch ( EOFException excepcionEOF ) {
        System.err.println( "EOF Cliente Fin Conexion" );
      }
    finally {        
        JSecureTransfer.jButton2.setEnabled(true);
        JSecureTransfer.jButton1.setText("Conectar!");
        JSecureTransfer.jTextField8.setText("No se ha levantado servidor");
        JSecureTransfer.jTextField5.setText("No se ha establecido conección");
        JSecureTransfer.jTextField15.setText("Desconectado");
        
        cerrarConexion(); // Paso 4: cerrar la conexi�n
      }

   } // fin del metodo ejecutarCliente

  /**
 *Metodo para conectarse con el servidor.
 */
   private static void conectarAServidor(String h,int p) throws IOException  // conectarse al servidor
   {

  
      mostrarMensaje( "Intentando realizar conexion\n" );

      // crear Socket para realizar la conexion con el servidor
      cliente = new Socket( InetAddress.getByName( h ), p );
     // archivo = new Socket( InetAddress.getByName( h ), p + 1);

      // mostrar la informaci�n de la conexion
      mostrarMensaje( "Conectado a: " +
         cliente.getInetAddress().getHostName() );
      JSecureTransfer.jTextField5.setText("Conectado a: " +
         cliente.getInetAddress().getHostName() );}
         

 /**
 *Metodo para declarar los flujos de salida y entrada.
 */
   private static void obtenerFlujos() throws IOException    // obtener flujos para enviar y recibir datos
   {
      // establecer flujo de salida para los objetos
      salida = new ObjectOutputStream( cliente.getOutputStream() );
      salida.flush(); // vaciar bufer de salida para enviar informacion de encabezado

      // establecer flujo de entrada para los objetos
      entrada = new ObjectInputStream( cliente.getInputStream() );

      mostrarMensaje( "\nSe recibieron los flujos de E/S\n" );
   }


 /**
 *Metodo para procesar la conexión establecida con el servidor.
 */
   private static void procesarConexion() throws IOException      // procesar la conexi�n con el servidor
   {
      // habilitar campoIntroducir para que el usuario del cliente pueda enviar mensajes
      establecerCampoTextoEditable( true ); 
      do { // procesar mensajes enviados del servidor
         // leer mensaje y mostrarlo en pantalla
         try {
             if(mensaje.equals("SERVIDOR>>> Ack-Env")) {    // Al recibir un Ack-Env el cliente sabe que el servidor esta listo para enviar un archivo
                 JSecureTransfer.jTextField11.setText("Hay un archivo por recibir...");
             }

             if(mensaje.equals("SERVIDOR>>> Nack")){
                System.out.println("Se recibio Nack");
                cerrarConexion();
                JSecureTransfer.jButton2.setEnabled(true);
                JSecureTransfer.jButton1.setText("Conectar!");
                JSecureTransfer.jTextField8.setText("No se ha levantado servidor");
                JSecureTransfer.jTextField5.setText("No se ha establecido coneccion");
                JSecureTransfer.jTextField15.setText("Desconectado");

                 break;
              }

              if(mensaje.equals("SERVIDOR>>> Nack2")){
                  env("encryptedKeyFile.txt");
             }

          else {
            mensaje = ( String ) entrada.readObject();
            mostrarMensaje( "\n" + mensaje );
            mensaje = ( String ) entrada.readObject();
            mostrarMensaje( "\n" + mensaje );
         }
         // atrapar los problemas que pueden ocurrir al leer del servidor
         }  catch ( ClassNotFoundException excepcionClaseNoEncontrada ) {
            mostrarMensaje( "\nSe recibio un objeto de tipo desconocido" );
         }
  
      } while ( !mensaje.equals( "TEST" ) );

   } // fin del metodo procesarConexion

  /**
 *Metodo para cerrar la conexion con el cliente.
 */
   public static void cerrarConexion()    // cerrar flujos y socket
   {
      mostrarMensaje( "\nCerrando conexion." );
      establecerCampoTextoEditable( false ); // deshabilitar campoIntroducir

      try {
          salida.close();
          entrada.close();
          cliente.close();

      }
      catch( IOException excepcionES ) {
        excepcionES.printStackTrace();
      }
   }

 /**
 *Metodo para enviar mensajes hacia el servidor e invocar al metodo para mostrarlos en el cuadro de texto.
 *@param mensaje Mensaje que se envia hacia el servidor.
 */
  public static void enviarDatos( String mensaje )
   {
      // enviar objeto al servidor
      try {
         salida.writeObject( "CLIENTE>>> " + mensaje );
         salida.flush();
         mostrarMensaje( "\nCLIENTE>>> " + mensaje );
      }

      // procesar los problemas que pueden ocurrir al enviar el objeto
      catch ( IOException excepcionES ) {
         JSecureTransfer.textArea1.append( "\nError al escribir el objeto" );
      }
   }

/**
 *Metodo para enviar mensajes hacia el servidor sin mostrarlos en el cuadro de texto.
 *@param mensaje Mensaje que se envia hacia el servidor.
 */
  public static void enviarDatossm( String mensaje )
   {
      // enviar objeto al servidor
      try {
         salida.writeObject( "CLIENTE>>> " + mensaje );
         salida.flush();
      }

      // procesar los problemas que pueden ocurrir al enviar el objeto
      catch ( IOException excepcionES ) {
         JSecureTransfer.textArea1.append( "\nError al escribir el objeto" );
      }
   }

  /**
 *Metodo para desplegar en el cuadro de texto los mensajes que se envian hacia el servidor.
 *@param mensajeAMostrar Mensaje que se muestra en el cuadro de texto.
 */
   private static void mostrarMensaje( final String mensajeAMostrar )
   {
      // mostrar mensaje del subproceso de ejecuci�n de la GUI
      SwingUtilities.invokeLater(
         new Runnable() {  // clase interna para asegurar que la GUI se actualice apropiadamente

            public void run() // actualiza areaPantalla
            {
               JSecureTransfer.textArea1.append( mensajeAMostrar );
               JSecureTransfer.textArea1.setCaretPosition(
                  JSecureTransfer.textArea1.getText().length() );
            }

         }  // fin de la clase interna

      ); // fin de la llamada a SwingUtilities.invokeLater
   }

  /**
 *Metodo que permite utilizar los cuadros de texto o dejarlos fijos sin que se pueda escribir en ellos.
 *@param editable controla si se puede escrbir o no en el cuadro de texto
 */
   private static void establecerCampoTextoEditable( final boolean editable )
   {
      // mostrar mensaje del subproceso de ejecucion de la GUI
      SwingUtilities.invokeLater(
         new Runnable() {  // clase interna para asegurar que la GUI se actualice apropiadamente

            public void run()  // establece la capacidad de modificar campoIntroducir
            {
               JSecureTransfer.jTextField13.setEditable( editable );
            }

         }  // fin de la clase interna

      ); // fin de la llamada a SwingUtilities.invokeLater
   }

} // fin de la clase Cliente
