/* 
* Copyright 2016 Agencia Estatal de Administración Tibutaria
*
* Licencia con arreglo a la EUPL, Versión 1.1 o –en cuanto
* sean aprobadas por la Comisión Europea– versiones
* posteriores de la EUPL (la «Licencia»);
* Solo podrá usarse esta obra si se respeta la Licencia.
* Puede obtenerse una copia de la Licencia en:
*  
* https://joinup.ec.europa.eu/software/page/eupl
*  
* Salvo cuando lo exija la legislación aplicable o se acuerde
* por escrito, el programa distribuido con arreglo a la
* Licencia se distribuye «TAL CUAL», SIN GARANTÍAS NI CONDICIONES
* DE NINGÚN TIPO, ni expresasni implícitas.
*
* Véase la Licencia en el idioma concreto que rige los permisos y 
* limitaciones que establece la Licencia.
*/ 

package sockets;

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

public class MessageSocket {

   private int puerto;
   private boolean procesarMensajes;
   private String host;
   private String separador = "\n";
   private String errorSocket;
   private boolean _abierto;
   private Socket socket = null;
   private PrintWriter out = null;
   private Thread hiloSocket;
   private StringBuffer sb;
   private EventListenerList listenerList;
   private java.text.NumberFormat nf;
   private boolean modoBin = false;

   public MessageSocket() {
      sb = new StringBuffer(4096);
      listenerList = new EventListenerList();
      nf = java.text.NumberFormat.getInstance();
      nf.setGroupingUsed(false);
      nf.setMinimumIntegerDigits(3);
   }

   public boolean open() {
      if (_abierto) {
         close();
      }
      try {
         socket = new Socket();
         socket.setReceiveBufferSize(32000);
         socket.connect(new InetSocketAddress(getHost(), getPuerto()));
         socket.setSoTimeout(0);
         _abierto = true;
         out = new PrintWriter(socket.getOutputStream());
         return iniciarHiloLectura();
      } catch (IOException ioexc) {
         fireErrorSocket(ioexc.getMessage(), 0);
         return false;
      }
   }

   public boolean close() {
      try {
         if (!_abierto) {
            return true;
         }
         hiloSocket.interrupt();
         socket.close();
         _abierto = false;
         return true;
      } catch (IOException ioexc) {
         return false;
      }
   }

   public void ponerSocketTimeout(int msegs) {
      try {
         socket.setSoTimeout(msegs);
      } catch(Exception exc) {}
   }

   public void quitarSocketTimeout() {
      try {
         socket.setSoTimeout(0);
      } catch(Exception exc) {}
   }

   boolean iniciarHiloLectura() {
      hiloSocket = new ThreadLecturaSocket();
      hiloSocket.start();
      return true;
   }

   void enviaBufer(byte[] bufer, int leidos) {
      fireBuferRecibido(bufer, leidos);
   }

   void procesaBufer(byte[] bufer, int leidos) {
      int largoInicial = sb.length();
      sb.append(new String(bufer, 0, leidos));
      int posInicio = 0;
      int posFinal;
      for (int i = 0; i < leidos; i++) {
         if (bufer[i] == 10) {  // caracter de salto de linea
            posFinal = largoInicial + i;
            if (i > 0 && bufer[i - 1] == 13) {
               posFinal--;
            }
            if (posFinal - posInicio > 0) {
               nuevoMensaje(sb.substring(posInicio, posFinal));
            }
            posInicio = largoInicial + i + 1;
         }
      }
      if (posInicio > 0) {
         sb.delete(0, posInicio);
      }
   }

   void nuevoMensaje(String msj) {
      if (procesarMensajes) {
         procesaMensaje(msj);
      } else {
         fireMensajeRecibido(msj, 0);
      }
   }

   void procesaMensaje(String msj) {
      int numMensaje = 999;
      String mensaje;
      try {
         numMensaje = Integer.parseInt(msj.substring(0, 3));
      } catch (Exception exc) {
      }
      if (numMensaje == 999) {
         mensaje = msj;
      } else {
         mensaje = msj.substring(3);
      }
      fireMensajeRecibido(mensaje, numMensaje);
   }

   public void addMessageSocketListener(MessageSocketListener listener) {
      listenerList.add(MessageSocketListener.class, listener);
   }

   public void removeMessageSocketListener(MessageSocketListener listener) {
      listenerList.remove(MessageSocketListener.class, listener);
   }

   void fireMensajeRecibido(String msj, int codigo) {
      MensajeRecibidoEvent ev = new MensajeRecibidoEvent(this, msj, codigo);
      EventListener[] listeners = listenerList.getListeners(MessageSocketListener.class);
      for (int i = 0; i < listeners.length; i++) {
         MessageSocketListener listener = (MessageSocketListener) listeners[i];
         listener.mensajeRecibido(ev);
      }
   }

   void fireBuferRecibido(byte[] bufer, int largo) {
      BuferRecibidoEvent ev = new BuferRecibidoEvent(this, bufer, largo);
      EventListener[] listeners = listenerList.getListeners(MessageSocketListener.class);
      for (int i = 0; i < listeners.length; i++) {
         MessageSocketListener listener = (MessageSocketListener) listeners[i];
         listener.buferRecibido(ev);
      }
   }

   void fireErrorSocket(String msj, int codigo) {
      MensajeRecibidoEvent ev = new MensajeRecibidoEvent(this, msj, codigo);
      EventListener[] listeners = listenerList.getListeners(MessageSocketListener.class);
      for (int i = 0; i < listeners.length; i++) {
         MessageSocketListener listener = (MessageSocketListener) listeners[i];
         listener.errorSocket(ev);
      }
   }

   public void enviar(String mensaje, int codigo) {
      String data;
      if (codigo == 999) {
         data = mensaje + separador;
      } else {
         data = nf.format(codigo) + mensaje + separador;
      }
      enviar(data);
   }

   public void enviar(String mensaje) {
      out.print(mensaje);
      out.flush();
   }

   void grabaBufer(byte[] bufer, int leidos) {
      String lt = "\n*************************************************\n";
      byte[] lineaTonta = lt.getBytes();
      try {
         FileOutputStream salida = new FileOutputStream("c:\\salida.bin", true);
         salida.write(bufer, 0, leidos);
         salida.write(lineaTonta);
         salida.close();

      } catch (Exception exc) {
      }
   }

   class ThreadLecturaSocket extends Thread {
      @Override
      public void run() {
         byte[] bufer = new byte[4096];
         try {
            InputStream inStr = socket.getInputStream();
            while (true) {
               int leidos = inStr.read(bufer);
               if (leidos == -1) {
                  throw (new SocketException("Connection: Desconectado del servidor"));
               }
               if (modoBin) {
                  enviaBufer(bufer, leidos);
               } else {
                  procesaBufer(bufer, leidos);
               }
            }
         } catch (SocketTimeoutException texc) {
            int timeout = 5000;
            try { timeout = socket.getSoTimeout(); } catch(Exception exc) {}
            fireErrorSocket("Se excedió el tiempo máximo de espera en lectura de socket (" +
                          timeout/1000 + " seg.)", 1);
         } catch (IOException ioexc) {
            fireErrorSocket(ioexc.getMessage(), 0);
         }  catch (Exception exc) {
            exc.printStackTrace();
         }
      }
   }

   public int getPuerto() {
      return puerto;
   }

   public void setPuerto(int puerto) {
      boolean reabrir = _abierto;
      if (reabrir) {
         close();
      }
      this.puerto = puerto;
      if (reabrir) {
         open();
      }
   }

   public void setProcesaMensajes(boolean procesaMensajes) {
      this.procesarMensajes = procesaMensajes;
   }

   public String getHost() {
      return host;
   }

   public void setHost(String host) {
      boolean reabrir = _abierto;
      if (reabrir) {
         close();
      }
      this.host = host;
      if (reabrir) {
         open();
      }
   }

   public Socket getSocket() {
      return socket;
   }

   public String ultimoError() {
      String s = errorSocket;
      errorSocket = "";
      return s;
   }

   public boolean abierto() {
      return _abierto;
   }

   public void setModoBin(boolean v) {
      modoBin = v;
   }

   public boolean isModoBin() {
      return modoBin;
   }
}


