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


/**
 * Clase que define los parametros necesarios para implementar un servidor que escuche y reciba
 * peticiones desde un cliente.
 * @author José Antonio Silva, Mario Ungemach, Jorge Mellings
 */
public class Server extends Thread {

 /**
 * objeto utilizado para escribir datos hacia el cliente.
 */
   private static ObjectOutputStream salida;
 /**
 * objeto utilizado para recibir datos desde el cliente.
 */
   private static ObjectInputStream entrada;
/**
 * objeto utilizado para implementar un socket con el que se comunica con el cliente,
 * a traves del cual poder enviar y recibir mensajes.
 */
   private static Socket conexion;
/**
 * objeto utilizado para implementar un socket con el que se comunica con el cliente
 *  a traves del cual poder enviar y recibir archivos.
 */
   private static Socket conexion2;
 /**
 * objeto utilizado para abrir un socket en el que se estara escuchando las peticiones
  * de los clientes.
 */
   private static ServerSocket servidor;
  /**
 * objeto utilizado para abrir un socket en el que se estara escuchando las peticiones
  * de los clientes. Este socket tiene el numero de puerto un digito mayor que el puerto
   * del socket servidor
 */
   private static ServerSocket archivo;

   /**
 * objeto utilizado para abrir un socket en el que se estara escuchando las peticiones
  * de los clientes. Este socket tiene el numero de puerto un digito mayor que el puerto
   * del socket servidor
 */

 /**
 * objeto utilizado para enviar mensajes hacia el cliente.
 */
   private static String mensaje = "";

      /**
 * objeto utilizado para definir si hay una conexion establecida con el cliente:
 * En caso de haber conexion su valor es 1 y en caso contrario es 0.
 */
   public static int flag_conexion = 0;



/**
 * Constructor de la clase Server.
 */
   public Server()
   {

   }



 /**
 *Metodo para invocar el metodo "enviaFichero(f)" que envia el fichero hacia el cliente
 *@param f String que contiene el nombre que tendra el fichero que se enviara
 */
public static void env(String f) {
              try {

            System.out.println("conexion_enc");
            enviaFichero(f);
           System.out.println("conexion");


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

    }

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

   pide(f);
   }

 /**
 *Metodo para enviar el fichero hacia el cliente.
 *@param fichero String que contiene el nombre del fichero a enviar.
 */
 public static void enviaFichero(String fichero)
    {
        try
        {

            conexion2 = archivo.accept();

            ObjectOutputStream oos = new ObjectOutputStream(conexion2.getOutputStream());

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

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

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

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

                // Se rellena el número de bytes leidos
                f_enckkey.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_enckkey.ultimoMensaje = true;
                    enviadoUltimo=true;
                }
                else
                    f_enckkey.ultimoMensaje = false;

                // Se envía por el socket
                oos.writeObject(f_enckkey);

                // Si es el último mensaje, salimos del bucle.
                if (f_enckkey.ultimoMensaje)
                    break;

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

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

            if (enviadoUltimo==false)
            {
                f_enckkey.ultimoMensaje=true;
                f_enckkey.bytesValidos=0;
                oos.writeObject(f_enckkey);
            }


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


  /**
 *Metodo para recibir el fichero desde el cliente.
 *@param fichero String que contiene el nombre que tendra el fichero que se reciba.
 */
 public static void pide(String fichero)
    {
        try
        {
   
            conexion2 = archivo.accept();
            // Se abre el socket.

            // Se abre un fichero para empezar a copiar lo que se reciba.
            FileOutputStream fos = new FileOutputStream(fichero);

            // Se crea un ObjectInputStream del socket para leer los mensajes
            // que contienen el fichero.
            ObjectInputStream ois = new ObjectInputStream(conexion
                    .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;
                    // Se escribe en pantalla y en el fichero
                    System.out.print(new String(
                            mensajeRecibido.contenidoFichero, 0,
                            mensajeRecibido.bytesValidos));
                    fos.write(mensajeRecibido.contenidoFichero, 0,
                            mensajeRecibido.bytesValidos);
                } 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();
    //        conexion2.close();
      //      archivo.close();
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }


  /**
 *Metodo para configurar y ejecutar el servidor.
 *@param puerto Numero de puerto que se usara para escuchar las peticiones.
 */
   public static  void ejecutarServidor(int puerto)throws IOException     // configurar y ejecutar el servidor
   {
      // configurar servidor para que reciba conexiones; procesar las conexiones
      try {

         //Primero se crea un objeto ServerSocket.
         servidor = new ServerSocket(puerto, 100 );
         archivo = new ServerSocket(puerto+1, 100 );

         while ( true ) {

            try {
               esperarConexion(); // El servidor queda esperando por una conexion.
               obtenerFlujos();        //Se obtenienen flujos de entrada y salida.
               procesarConexion(); //Se procesa la conexion.
            }
            catch ( EOFException excepcionEOF ) {
                System.err.println( "EOF Servidor Fin Conexion" );
            }
         }
        }
     finally {
        cerrarConexion(); // Se cierra la conexion
      }
    // fin del metodo ejecutarServidor
   }


  /**
 *Metodo que se ejecuta para esperar que un cliente se conecte.
 */
   private static void esperarConexion() throws IOException // esperar que la conexion llegue, despues mostrar informaci0n de la conexion
   {
      mostrarMensaje( "Esperando una conexion\n" );
      conexion = servidor.accept();
 //     conexion2 = archivo.accept();// permitir al servidor aceptar la conexi�n
      mostrarMensaje( "Conexion recibida de: " +
         conexion.getInetAddress().getHostName() );
   }


  /**
 *Metodo para desplegar en el cuadro de texto los mensajes que se envian hacia el cliente.
 *@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 despachador de eventos
      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 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( conexion.getOutputStream() );
      salida.flush(); // vaciar b�fer de salida para enviar informaci�n de encabezado

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

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

 /**
 *Metodo para procesar la conexión establecida con el cliente.
 */
   private static void procesarConexion() throws IOException  // procesar la conexion con el cliente
   {
      // enviar mensaje de conexi�n exitosa al cliente
       flag_conexion = 1; // Hay conexion
      String mensaje = "Conexion exitosa";
  enviarDatos( mensaje );
      System.out.println( "paso 1" );

      establecerCampoTextoEditable( true );

      do { // procesar los mensajes enviados por el cliente


           // leer el mensaje y mostrarlo en pantalla
         // leer el mensaje y mostrarlo en pantalla
         try {
                if(mensaje.equals("CLIENTE>>> Nack")){
                System.out.println("Se recibio Nack");
                cerrarConexion();
                JSecureTransfer.jButton1.setEnabled(true);

                JSecureTransfer.jButton2.setText("Levantar!");
                JSecureTransfer.jTextField8.setText("No se ha levantado servidor");
                JSecureTransfer.jTextField5.setText("No se ha establecido conección");
                JSecureTransfer.jTextField15.setText("Desconectado");

                 break;}
                 
                     if(mensaje.equals("CLIENTE>>> Nack2")){    // Al recibir un Nack2 el Servidor sabe que el cliente esta listo para enviar un archivo.
   
            System.out.println("Se recibio Nack2");
             pedir("encryptedFile.txt");

             }   else if(mensaje.equals("CLIENTE>>> Nack3")){    // Al recibir un Nack3 el Servidor sabe que el cliente esta listo para recibir un archivo.
              System.out.println("Se recibio Nack3");
             env("encryptedFile.txt");                             // Primero se envia el archivo encriptado

             }
             else if(mensaje.equals("CLIENTE>>> Nack4")) {        // Al recibir un Nack4 el Servidor sabe que el cliente recibio el archivo encriptado.
                  System.out.println("Se recibio Nack4");
                  JSecureTransfer.jTextField11.setText("Archivo enviado con exito!");
             env("encryptedKeyFile.txt");       // Se envia la llave encriptada.

             }else{ mensaje = ( String ) entrada.readObject();
            mostrarMensaje( "\n" + mensaje );
             }
 
         }

         // atrapar problemas que pueden ocurrir al tratar de leer del cliente
         catch ( ClassNotFoundException excepcionClaseNoEncontrada ) {
            mostrarMensaje( "\nSe recibi� un tipo de objeto desconocido" );
         }
     System.out.println( "paso 2" );
      } while ( !mensaje.equals( "CLIENTE>>> TERMINAR" ) );
   System.out.println( "paso 3" );
   } // fin del metodo procesarConexion


  /**
 *Metodo para cerrar la conexion con el cliente.
 */
   public static void cerrarConexion()      // cerrar flujos y socket
   {
      mostrarMensaje( "\nCerrando Conexion!\n" );
      establecerCampoTextoEditable( false ); // deshabilitar campoIntroducir
       
      try {
            conexion.close();
            servidor.close();
            archivo.close();
      }
      catch( IOException excepcionES ) {
         excepcionES.printStackTrace();
      }
      flag_conexion = 0;
   }

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

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


 /**
 *Metodo para enviar mensajes hacia el cliente sin mostrarlos en el cuadro de texto.
 *@param mensaje Mensaje que se envia hacia el cliente.
 */
   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 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 despachador de eventos
      SwingUtilities.invokeLater(
         new Runnable() {  // clase interna para asegurar que la GUI se actualice apropiadamente

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

         }  // fin de la clase interna

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



}  // fin de la clase Servidor
