/*
 * Copyright (C) 2005 Luca Veltri - University of Parma - Italy
 * 
 * This source code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This source code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this source code; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * Author(s):
 * Luca Veltri (luca.veltri@unipr.it)
 */

package local.net;


import org.zoolu.tools.LogLevel;

import java.net.*;
import java.io.InterruptedIOException;


/** UdpRelay implements a direct UDP datagram relay agent. 
  * It receives UDP packets at a local port and relays them toward a remote UDP socket
  * (destination address/port).
  */
public class UdpRelay extends Thread
{
   // The maximum IP packet size
   //public static final int MAX_PKT_SIZE=65536;
   public static final int MAX_PKT_SIZE=2000;

   /** Local receiver/sender port */
   int local_port;  
   /** Remote source address */
   String src_addr;
   /** Remote source port */
   int src_port;  
   /** Destination address */
   String dest_addr;
   /** Destination port */
   int dest_port;  
   /** Whether it is running */
   boolean stop;
   /** Maximum time that the UDP relay can remain active after been halted (in milliseconds) */
   int socket_to=3000; // 3sec 
   /** Maximum time that the UDP relay remains active without receiving UDP datagrams (in seconds) */
   int alive_to=60; // 1min 
   /** UdpRelay listener */
   UdpRelayListener listener;   
     
   /** Creates a new UDP relay and starts it.
     * <p> The UdpRelay remains active until method halt() is called. */
   public UdpRelay(int local_port, String dest_addr, int dest_port, UdpRelayListener listener)
   {  init(local_port,dest_addr,dest_port,0,listener);
      start();
   }

   /** Creates a new UDP relay and starts it.
     * <p> The UdpRelay will automatically stop after <i>alive_time</i> seconds
     *     of idle time (i.e. without receiving UDP datagrams) */
   public UdpRelay(int local_port, String dest_addr, int dest_port, int alive_time, UdpRelayListener listener)
   {  init(local_port,dest_addr,dest_port,alive_time,listener);
      start();
   }
    
   /** Inits a new UDP relay */
   private void init(int local_port, String dest_addr, int dest_port, int alive_time, UdpRelayListener listener)
   {  this.local_port=local_port;     
      this.dest_addr=dest_addr;
      this.dest_port=dest_port;
      this.alive_to=alive_time;
      this.listener=listener;
      src_addr="0.0.0.0";
      src_port=0;
      stop=false;
   }

   /** Gets the local receiver/sender port */
   public int getLocalPort()
   {  return local_port;
   }

   /** Gets the destination address */
   /*public String getDestAddress()
   {  return dest_addr;
   }*/

   /** Gets the destination port */
   /*public int getDestPort()
   {  return dest_port;
   }*/

   /** Sets a new destination address */
   public UdpRelay setDestAddress(String dest_addr)
   {  this.dest_addr=dest_addr;
      return this;
   }

   /** Sets a new destination port */
   public UdpRelay setDestPort(int dest_port)
   {  this.dest_port=dest_port;
      return this;
   }

   /** Whether the UDP relay is running */
   public boolean isRunning()
   {  return !stop;
   }

   /** Stops the UDP relay */
   public void halt()
   {  stop=true;
   }

   /** Sets the maximum time that the UDP relay can remain active after been halted */
   public void setSoTimeout(int so_to)
   {  socket_to=so_to;
   }

   /** Gets the maximum time that the UDP relay can remain active after been halted */
   public int getSoTimeout()
   {  return socket_to;
   }
       
   /** Redirect packets received from remote source addr/port to destination addr/port  */
   public void run()
   {  //System.out.println("DEBUG: starting UdpRelay "+toString()+" (it expires after "+alive_to+" sec)");     
      try   
      {  DatagramSocket socket=new DatagramSocket(local_port);
         byte []buf=new byte[MAX_PKT_SIZE];
                           
         socket.setSoTimeout(socket_to);
         // datagram packet
         DatagramPacket packet=new DatagramPacket(buf, buf.length);
         
         // convert alive_to in milliseconds
         long keepalive_to=((1000)*(long)alive_to)-socket_to;
         
         // end time
         long expire=System.currentTimeMillis()+keepalive_to;
         // whether reset the receiver
         //boolean reset=true;
         
         while(!stop)
         {  // non-blocking receiver
            try
            {  socket.receive(packet);           
            }
            catch (InterruptedIOException ie)
            {  // if expired, stop relaying
               if (alive_to>0 && System.currentTimeMillis()>expire) halt();
               continue;
            }
            // check whether the source address and port are changed
            if (src_port!=packet.getPort() || !src_addr.equals(packet.getAddress().getHostAddress().toString()))
            {  src_port=packet.getPort();
               src_addr=packet.getAddress().getHostAddress();
               if (listener!=null) listener.onUdpRelaySourceChanged(this,src_addr,src_port);
            }
            // relay
            packet.setAddress(InetAddress.getByName(dest_addr));
            packet.setPort(dest_port);
            socket.send(packet);
            // reset
            packet=new DatagramPacket(buf, buf.length);
            expire=System.currentTimeMillis()+keepalive_to;
         }
         socket.close();
         if (listener!=null) listener.onUdpRelayTerminated(this);
      }
      catch (Exception e) { e.printStackTrace(); } 

      //System.out.println("DEBUG: closing UdpRelay "+toString())");     
   }
   
   
      
   /** Gets a String representation of the Object */
   public String toString()
   {  return Integer.toString(local_port)+"-->"+dest_addr+":"+dest_port;
   }


   // ********************************** MAIN *********************************

   /** The main method. */
   public static void main(String[] args)
   {  
      if (args.length<3)
      {
         System.out.println("usage:\n   java UdpRelay <local_port> <address> <port> [ <alive_time> ]");
         System.exit(0);
      }
      
      int local_port=Integer.parseInt(args[0]);
      int remote_port=Integer.parseInt(args[2]);
      String remote_address=args[1];
      
      int alive_time=0;
      if (args.length>3) alive_time=Integer.parseInt(args[3]);
      
      new UdpRelay(local_port,remote_address,remote_port,alive_time,null);
   }
}
 