package internet;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;


/**
 * Sends text messages to a UDP port in a separate thread of execution 
 *
 * @version 1.0
 * @author  Prof. David Bernstein, James Madison University
 */
public class UDPMessageSender implements Runnable
{
    private volatile boolean              keepRunning;
    private DatagramSocket                ds;
    private InetAddress                   ipAddress;
    private int                           port;
    private List<String>                  messageQueue;    
    private Object                        signal;    
    private Thread                        controlThread;

    

    /**
     * Explicit Value Constructor
     *
     * @param address   The IP addres to send to
     */
    public UDPMessageSender(InetAddress address) 
       throws SocketException
    {
       this(address, 9001);
    }

    /**
     * Explicit Value Constructor
     *
     * @param address   The IP addres to send to
     * @param port      The UDP port to send to
     */
    public UDPMessageSender(InetAddress address, 
			    int port) 
       throws SocketException
    {
       // Remember the destination address and port
       ipAddress = address;
       this.port = port;
       
       //[1
       // Construct a DatagramSocket using any available port
       ds = new DatagramSocket();
       //]1

       // Construct the message queue
       // (If needed, one could use a java.util.concurrent.BlockingQueue)
       messageQueue = Collections.synchronizedList(new LinkedList<String>());

       // Construct the Object that is used to signal the 
       // state of the messageQueue
       signal = new Object();
    }


    /**
     * The code that is executed in the controlThread object's
     * thread of execution
     */
    public void run()
    {
       String            message;
       

       while (keepRunning)
       {
          while (messageQueue.size() > 0)
          {
             // Remove the message at the front of the queue (Note:
             // Since messageQueue is "wrapped", unsynchronized access
             // is prevented)
             message = messageQueue.remove(0);
             transmit(message);
          }
             
          // Enter the wait state until the messageQueue isn't empty
          synchronized(signal)
          {
             try
             {
                // Check to make sure nothings been added to the queue
                // in another thread
                if (messageQueue.size() == 0) 
                {
                   signal.wait();
                }
             }
             catch (InterruptedException ie)
             {
                // This probably means stop() was called.  If not,
                // just try again.
             }
          }
       }
       
       controlThread = null;
    }


    /**
     * Send a message.
     *
     * Actually, this method puts the message in a queue.  The
     * message will actually be sent in another thread of execution.
     *
     * @param message  The message to send
     */
    public void send(String message)
    {
       // Notify other threads that the messageQueue has changed
       synchronized(signal)
       {
          // Add to the end of the queue (Note: Since messageQueue is
          // "wrapped", unsynchronized access is prevented).
          messageQueue.add(message);

          // It is possible that the message was already handled in
          // another thread making this notification extraneous, but
          // it doesn't hurt.  (We could, instead, check
          // messageQueue.size() first.)
          signal.notifyAll();
       }
    }
    

    /**
     * Set the IP address to send to
     *
     * @param address   The new IP address number
     */
    public void setAddress(InetAddress address)
    {
       this.ipAddress = address;
    }




    /**
     * Set the port number to send to
     *
     * @param port   The new port number
     */
    public void setPort(int port)
    {
       this.port = port;
    }


    /**
     * Start the thread of execution that actually sends
     * the messages
     */
    public void start()
    {
       if (controlThread == null)
       {
          controlThread = new Thread(this);
          keepRunning = true;

          controlThread.start();
       }
    }
    


    /**
     * Stop the thread of execution (after it finishes sending
     * messages)
     */
    public void stop()
    {
       keepRunning = false;

       // Force the thread out of the wait state (if necessary)
       controlThread.interrupt();
    }


    /**
     * Actually transmit a message
     *
     * @param message   The message
     */
    private void transmit(String message)
    {
       //[2
       byte[]                data;
       DatagramPacket        dp;


       try
       {
          // Convert the String to a byte array
          data = message.getBytes();
           
          // Construct a packet (that includes the address and port)
          dp = new DatagramPacket(data,data.length,
                                  ipAddress,port);
           
          // Send the packet
          ds.send(dp);
       } 
       catch (IOException ioe) 
       {
          // To debug: ioe.printStackTrace();
       }
       //]2
    }
}
