package internet;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;


/**
 * Receives text messages (with a maximum length of 255 bytes) on a
 * UDP port and informs observers of those messages in a separate
 * thread of execution
 *
 * @version 1.0
 * @author  Prof. David Bernstein, James Madison University
 */
public class UDPMessageReceiver implements Runnable
{
    private volatile boolean                      keepRunning;
    private DatagramSocket                        ds;
    private CopyOnWriteArrayList<MessageListener> listeners;
    private int                                   port;
    private Thread                                controlThread;


    /**
     * Explicit Value Constructor
     *
     * @param port       The UDP port to listen to
     */
    public UDPMessageReceiver(int port) throws IOException
    {
       this.port = port;
       keepRunning = true;
       
       //[1
       ds = new DatagramSocket(port); // throws IOException
       //]1
       // Make sure that receive() times-out so the thread can be stopped
       ds.setSoTimeout(5000);
       

       // A CopyOnWriteArrayList is used because: we need to preclude
       // interference across threads, iterations vastly outnumber
       // mutations, and we don't want to synchronize iterations
       listeners = new CopyOnWriteArrayList<MessageListener>();
    }

			    

    /**
     * Add a MessageListener
     *
     * @param listener   The MessageListener to add
     */
    public void addMessageListener(MessageListener listener)
    {
       // This may be slow (because listeners is a CopyOnWriteArrayList)
       // but shouldn't happen frequently
       listeners.add(listener);
    }
    


    /**
     * Notify all MessageListener objects 
     *
     * @param message   The text message
     */
    public void notifyListeners(String message)
    {
       Iterator<MessageListener>       i;       
       MessageListener                 listener;

       i = listeners.iterator();
       while (i.hasNext())
       {
          listener = i.next();
          listener.handleMessage(message);
       }
    }



    /**
     * Remove a MessageListener
     *
     * @param listener   The MessageListener to remove
     */
    public void removeMessageListener(MessageListener listener)
    {
       // This may be slow (because listeners is a CopyOnWriteArrayList)
       // but shouldn't happen frequently
       listeners.remove(listener);
    }



    /**
     * Run this Receiver
     */
    public void run()
    {
       //[2
       byte[]           buffer;
       DatagramPacket   in;
       int              bufferLength;
       String           line;
       
       
       bufferLength = 255;
       //]2
       while (keepRunning)
       {
          //[2
          try
          {
             // Construct an empty byte array
             buffer = new byte[bufferLength];

             // Construct an empty DatagramPacket
             in = new DatagramPacket(buffer, buffer.length);

             // Block until an incoming packet is received,
             // then fill the byte array
             ds.receive(in);
             
             // Construct a String from the byte array
             line=new String(in.getData(), 0, in.getLength());

             //]2   
             // Notify all listeners that a packet has arrived
             notifyListeners(line);
             //[2
          }
          //]2
          catch (SocketTimeoutException ste)
          {
             // receive() timed-out.  Check keepRunning and proceed.
          }
          //[2
          catch (IOException ioe)
          {
             // receive() had a problem.  Re-try.
          }
          //]2
       }
       controlThread = null;       
    }


    /**
     * Start the thread of execution that actually receives
     * the messages
     */
    public void start()
    {
       if (controlThread == null)
       {
          controlThread = new Thread(this);
          keepRunning = true;

          controlThread.start();
       }
    }


    /**
     * Stop the thread of execution (after it finishes receiving
     * messages)
     */
    public void stop()
    {
       keepRunning = false;
    }

}
